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

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

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

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

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

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

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

Ответ 1

Одна идея:

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

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

Ответ 2

Переместите метод factory из этого типа и поместите его в свой класс factory.

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 в свой класс factory (хотя я думаю, что он предпочтительнее - разделение проблем), вы можете поместить его в Класс фруктов:

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

Ответ 3

Почему бы не создать класс factory (templated) с помощью метода create?

FruitFactory<Banana>.Create();

Ответ 4

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

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

Ответ 5

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

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

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.

Ответ 6

Прежде всего, не имея статических инициализаторов, которые могут быть виртуальными, не означает, что у вас не могут быть "стандартные" методы-члены, которые могут быть перегружены. Во-вторых, вы можете вызывать свои виртуальные методы от конструкторов, и они будут работать так, как ожидалось, поэтому здесь проблем нет. В-третьих, вы можете использовать generics для безопасного типа factory.
Вот некоторый код, который использует метод 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());
    }
}

Ответ 7

Я бы сказал, что лучше всего создать виртуальный/абстрактный метод Initialise для класса фруктов, который должен быть вызван, а затем создать внешний класс "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;
    }
}