Какова правильная альтернатива наследованию статического метода?


Я понимаю, что наследование статического метода не поддерживается в C#. Я также прочитал ряд обсуждений (в том числе здесь), в которых разработчики заявляют о необходимости этой функциональности, на что типичный ответ: "Если вам нужно статическое наследование членов, в вашем дизайне есть недостаток".

хорошо, учитывая, что ООП не хочет, чтобы я даже думал о статическом наследовании, я должен заключить, что моя очевидная потребность в нем указывает на ошибку в моем дизайне. Но, я застрял. Я бы очень ценю некоторую помощь в решении этой проблемы. Вот в чем проблема ...

Я хочу создать абстрактный базовый класс (назовем его фруктом), который инкапсулирует некоторый сложный код инициализации. Этот код не может быть помещен в конструктор, так как некоторые из них будут зависеть от вызовов виртуальных методов.

фрукты будут унаследованы другими конкретными классами (Apple, Orange), каждый из которых должен предоставить стандартный заводской метод CreateInstance() для создания и инициализации пример.

Если бы наследование статических членов было возможно, я бы поместил метод фабрики в базовый класс и использовал вызов виртуального метода производному классу для получения типа, из которого должен быть инициализирован конкретный экземпляр. Клиентский код будет просто вызывать Apple.CreateInstance () для получения полностью инициализированного экземпляра Apple.

но ясно, что это невозможно, поэтому может кто-нибудь объяснить, как мой дизайн должен измениться, чтобы разместить то же самое функциональность.

7 74

7 ответов:

одна идея:

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() { ... }
}

и звонок Вот так:

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

никаких дополнительных классов фабрики не требуется.

переместите заводской метод из типа и поместите его в свой собственный Заводской класс.

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();
     }
}

но, поскольку я смотрю на это, нет необходимости перемещать метод Create в свой собственный класс фабрики (хотя я думаю, что это предпочтительно-разделение проблем -), вы можете поместить его в класс фруктов:

public abstract class Fruit
{

   public abstract string Define();

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

}

и, чтобы увидеть, если это работает:

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

            Console.ReadLine ();
        }        
    }

почему бы не создать фабричный класс (шаблонный) с помощью метода create?

FruitFactory<Banana>.Create();

Я бы сделал что-то вроде этого

 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()

The WebRequest класс и его производные типы в .NET BCL представляют собой хороший пример того, как этот вид дизайна может быть реализован относительно хорошо.

The WebRequest класс имеет несколько подклассов, в том числе HttpWebRequest и FtpWebReuest. Так вот, это WebRequest базовый класс также является заводским типом и предоставляет статический Create метод (конструкторы экземпляра скрыты, как того требует заводской шаблон).

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

этот Create метод возвращает конкретную реализацию WebRequest класс и использует URI (или строку URI) для определения типа объекта для создания и возврата.

это имеет конечный результат следующего шаблона использования:

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/");

я лично думаю, что это хороший способ подойти к проблеме, и это действительно кажется предпочтительным методом создателей .NET Framework.

во-первых, отсутствие статических инициализаторов, которые могут быть виртуальными, не означает, что у вас не может быть "стандартных" методов-членов, которые могут быть перегружены. Во-вторых, вы можете вызвать свои виртуальные методы из конструкторов, и они будут работать так, как ожидалось, поэтому здесь нет проблем. В-третьих, вы можете использовать дженерики, чтобы иметь типобезопасную фабрику.
Вот некоторый код, который использует factory + member Initialize () метод, который вызывается конструктором (и он защищен, поэтому вам не нужно беспокоиться, что кто-то вызовет его снова после создания объекта):


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());
    }
}

Я бы сказал, что лучше всего создать виртуальный / абстрактный метод инициализации для класса fruit, который должен быть вызван, а затем создать внешний класс "fruit factory" для создания экземпляров:


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;
    }
}