Giải pháp thay thế chính xác cho kế thừa phương thức tĩnh là gì?


83

Tôi hiểu rằng kế thừa phương thức tĩnh không được hỗ trợ trong C #. Tôi cũng đã đọc một số cuộc thảo luận (bao gồm cả ở đây) trong đó các nhà phát triển tuyên bố cần có chức năng này, mà câu trả lời điển hình là "nếu bạn cần kế thừa thành viên tĩnh, có một lỗ hổng trong thiết kế của bạn".

OK, cho rằng OOP không muốn tôi nghĩ về kế thừa tĩnh, tôi phải kết luận rằng nhu cầu rõ ràng của tôi đối với nó chỉ ra một lỗi trong thiết kế của tôi. Nhưng, tôi bị mắc kẹt. Tôi thực sự đánh giá cao một số trợ giúp giải quyết vấn đề này. Đây là thử thách ...

Tôi muốn tạo một lớp cơ sở trừu tượng (hãy gọi nó là Fruit) đóng gói một số mã khởi tạo phức tạp. Mã này không thể được đặt trong hàm tạo, vì một số mã sẽ dựa vào các lệnh gọi phương thức ảo.

Fruit sẽ được kế thừa bởi các lớp cụ thể khác (Apple, Orange), mỗi lớp phải hiển thị một phương thức nhà máy tiêu chuẩn CreateInstance () để tạo và khởi tạo một thể hiện.

Nếu việc kế thừa thành viên tĩnh là khả thi, tôi sẽ đặt phương thức gốc trong lớp cơ sở và sử dụng một lệnh gọi phương thức ảo đến lớp dẫn xuất để lấy kiểu mà từ đó một cá thể cụ thể phải được khởi tạo. Mã máy khách sẽ đơn giản gọi Apple.CreateInstance () để có được một phiên bản Apple được khởi tạo đầy đủ.

Nhưng rõ ràng điều này là không thể, vì vậy ai đó có thể vui lòng giải thích cách thiết kế của tôi cần thay đổi như thế nào để phù hợp với chức năng tương tự.


2
Tôi muốn chỉ ra rằng tuyên bố "Không thể đặt mã này trong hàm tạo, vì một số mã sẽ dựa vào các lệnh gọi phương thức ảo" là không đúng. Bạn CÓ THỂ gọi các phương thức ảo bên trong một hàm tạo. (Bạn sẽ phải thiết kế các cuộc gọi của mình một cách cẩn thận, vì bạn có thể có một lớp được khởi tạo một phần khi các phương thức ảo được gọi, nhưng nó được hỗ trợ.) Xem câu trả lời của Ravadre để biết thêm chi tiết.
Ruben

Câu trả lời:


61

Một ý tưởng:

public abstract class Fruit<T>
    where T : Fruit<T>, new()
{
    public static T CreateInstance()
    {
        T newFruit = new T();
        newFruit.Initialize();  // Calls Apple.Initialize
        return newFruit;
    }

    protected abstract void Initialize();
}

public class Apple : Fruit<Apple>
{
    protected override void Initialize() { ... }
}

Và gọi như vậy:

Apple myAppleVar = Fruit<Apple>.CreateInstance();

Không cần các lớp học thêm tại nhà máy.


9
IMHO, tốt hơn là nên chuyển kiểu chung sang phương thức CreateInstance thay vì đặt nó ở cấp độ lớp. (Giống như tôi đã làm trong câu trả lời của mình).
Frederik Gheysels

1
Sở thích của bạn được ghi nhận. Một lời giải thích ngắn gọn về sở thích của bạn sẽ được đánh giá cao hơn.
Matt Hamsmith vào

9
Làm như vậy, bạn không làm 'ô nhiễm' phần còn lại của lớp; tham số kiểu chỉ cần thiết trong phương thức Create (ít nhất là trong ví dụ này). Bên cạnh đó, tôi nghĩ rằng: Fruit.Create <Apple> (); thì dễ đọc hơn: Fruit <Apple> .Create ();
Frederik Gheysels,

2
Bạn không thể tạo hàm tạo Appleít hơn công khai, vì where T : Fruit<T>, new()lệnh Tphải có hàm tạo công khai. @Matt Hamsmith - Mã của bạn không biên dịch cho đến khi bạn xóa protected Apple() { }. Tôi đã thử nghiệm nó trong VS.
Tohid 13/02/13

1
@Tohid - Bạn nói đúng. Tôi đã chỉnh sửa. Tuy nhiên, điều này khiến Apple được xây dựng mà không sử dụng phương pháp nhà máy tĩnh Fruit CreateInstance, nhưng điều đó dường như không thể tránh khỏi.
Matt Hamsmith

17

Di chuyển phương thức factory ra khỏi kiểu và đặt nó vào lớp Factory của chính nó.

public abstract class Fruit
{
    protected Fruit() {}

    public abstract string Define();

}

public class Apple : Fruit
{
    public Apple() {}

    public override string Define()
    {
         return "Apple";
    }
}

public class Orange : Fruit
{
    public Orange() {}

    public override string Define()
    {
         return "Orange";
    }
}

public static class FruitFactory<T> 
{
     public static T CreateFruit<T>() where T : Fruit, new()
     {
         return new T();
     }
}

Tuy nhiên, khi tôi đang xem xét điều này, không cần phải di chuyển phương thức Create sang lớp Factory của chính nó (mặc dù tôi nghĩ rằng nó thích hợp hơn - điều chỉnh mối quan tâm-), bạn có thể đặt nó vào lớp Fruit:

public abstract class Fruit
{

   public abstract string Define();

   public static T CreateFruit<T>() where T : Fruit, new()
   {
        return new T();
   }

}

Và, để xem nó có hoạt động không:

    class Program
    {
        static void Main( string[] args )
        {
            Console.WriteLine (Fruit.CreateFruit<Apple> ().Define ());
            Console.WriteLine (Fruit.CreateFruit<Orange> ().Define ());

            Console.ReadLine ();
        }        
    }

1
Mã của bạn sẽ không biên dịch. bạn cần mệnh đề where T: new (). Tuy nhiên, đây là một bản dupe chính xác của tôi.
John Gietzen

1
Mặc dù người ta có thể tạo một lớp FruitFactory tĩnh, nhưng tôi sẽ ưu tiên giao diện IFruitFactory, cùng với một lớp FruitFactory có thể khởi tạo. Bạn có thể chỉ có một lớp FruitFactory, có phương thức CreateFruit không tham chiếu đến cá thể đối tượng của nó và do đó cũng có thể là một phương thức tĩnh, nhưng nếu nó trở nên cần thiết, hãy cung cấp nhiều cách tạo quả hoặc nếu tạo quả luôn yêu cầu khả năng duy trì trạng thái, sử dụng các phương thức phiên bản có thể tỏ ra hữu ích.
supercat

4

Tại sao không tạo một lớp nhà máy (mẫu) bằng phương thức create?

FruitFactory<Banana>.Create();

4

Tôi sẽ làm một cái gì đó như thế này

 public abstract class Fruit() {
      public abstract void Initialize();
 }

 public class Apple() : Fruit {
     public override void Initialize() {

     }
 }

 public class FruitFactory<T> where T : Fruit, new {
      public static <T> CreateInstance<T>() {
          T fruit = new T();
          fruit.Initialize();
          return fruit;  
      }
 } 


var fruit = FruitFactory<Apple>.CreateInstance()

3

Các WebRequest lớp và các loại phái sinh của nó trong NET BCL đại diện cho một ví dụ tốt về cách loại thiết kế này có thể được thực hiện tương đối tốt.

Các WebRequestlớp có một số phụ lớp, bao gồm HttpWebRequestFtpWebReuest. Bây giờ, WebRequestlớp cơ sở này cũng là một kiểu nhà máy và hiển thị một Createphương thức tĩnh (các hàm tạo cá thể được ẩn, theo yêu cầu của mẫu nhà máy).

public static WebRequest Create(string requestUriString)
public static WebRequest Create(Uri requestUri)

CreatePhương thức này trả về một triển khai cụ thể củaWebRequest lớp và sử dụng URI (hoặc chuỗi URI) để xác định loại đối tượng để tạo và trả về.

Điều này có kết quả cuối cùng của kiểu sử dụng sau:

var httpRequest = (HttpWebRequest)WebRequest.Create("http://stackoverflow.com/");
// or equivalently
var httpRequest = (HttpWebRequest)HttpWebWebRequest.Create("http://stackoverflow.com/");

var ftpRequest = (FtpWebRequest)WebRequest.Create("ftp://stackoverflow.com/");
// or equivalently
var ftpRequest = (FtpWebRequest)FtpWebWebRequest.Create("ftp://stackoverflow.com/");

Cá nhân tôi nghĩ rằng đây là một cách tốt để tiếp cận vấn đề và nó thực sự có vẻ là phương pháp ưa thích của những người tạo .NET Framework.


3

Trước hết, việc không có các trình khởi tạo tĩnh có thể là ảo không có nghĩa là bạn không thể có các phương thức thành viên "chuẩn", điều đó có thể bị quá tải. Trước hết, bạn có thể gọi các phương thức ảo của mình từ các hàm tạo và chúng sẽ hoạt động như mong đợi, vì vậy không có vấn đề gì ở đây. Thứ ba, bạn có thể sử dụng generic để có nhà máy an toàn về loại.
Đây là một số đoạn mã sử dụng phương thức factory + member Initialize () được gọi bởi hàm tạo (và nó được bảo vệ, vì vậy bạn không cần phải lo lắng rằng ai đó sẽ gọi lại nó sau khi tạo một đối tượng):


abstract class Fruit
{
    public Fruit()
    {
        Initialize();
    }

    protected virtual void Initialize()
    {
        Console.WriteLine("Fruit.Initialize");
    }
}

class Apple : Fruit
{
    public Apple()
        : base()
    { }

    protected override void Initialize()
    {
        base.Initialize();
        Console.WriteLine("Apple.Initialize");
    }

    public override string ToString()
    {
        return "Apple";
    }
}

class Orange : Fruit
{
    public Orange()
        : base()
    { }

    protected override void Initialize()
    {
        base.Initialize();
        Console.WriteLine("Orange.Initialize");
    }

    public override string ToString()
    {
        return "Orange";
    }
}

class FruitFactory
{
    public static T CreateFruit<T>() where T : Fruit, new()
    {
        return new T();
    }
}

public class Program
{

    static void Main()
    {
        Apple apple = FruitFactory.CreateFruit<Apple>();
        Console.WriteLine(apple.ToString());

        Orange orange = new Orange();
        Console.WriteLine(orange.ToString());

        Fruit appleFruit = FruitFactory.CreateFruit<Apple>();
        Console.WriteLine(appleFruit.ToString());
    }
}

1
Nói chung, tốt nhất là tránh gọi các phương thức ảo từ các hàm tạo. Xem blogs.msdn.com/abhinaba/archive/2006/02/28/540357.aspx
TrueWill

Đó là sự thật, nó có thể phức tạp, mặc dù, nó có thể và sẽ luôn hoạt động như nó cần, vấn đề là, hiểu 'nó nên' nghĩa là gì. Nói chung, tôi có xu hướng tránh gọi bất kỳ phương thức nào phức tạp hơn (ngay cả không ảo, tức là. Bởi vì chúng thường được xây dựng theo một cách, cho phép chúng ném một ngoại lệ và tôi không thích ctors của mình để ném bất kỳ thứ gì) , nhưng đó là quyết định của nhà phát triển.
Marcin Deptuła

0

Tôi muốn nói điều tốt nhất nên làm là tạo một phương thức Initialise ảo / trừu tượng trên lớp fruit mà phải được gọi và sau đó tạo một lớp 'fruit factory' bên ngoài để tạo các thể hiện:


public class Fruit
{
    //other members...
    public abstract void Initialise();
}

public class FruitFactory()
{
    public Fruit CreateInstance()
    {
        Fruit f = //decide which fruit to create
        f.Initialise();

        return f;
    }
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.