Pregunta ¿Cuál es la alternativa correcta a la herencia de métodos estáticos?


Entiendo que la herencia de métodos estáticos no es compatible con C #. También he leído varias discusiones (incluso aquí) en las que los desarrolladores afirman que es necesario contar con esta funcionalidad, a lo que la respuesta típica es "si necesita herencia de miembros estáticos, hay un defecto en su diseño".

De acuerdo, dado que OOP no quiere que siquiera piense en la herencia estática, debo concluir que mi aparente necesidad apunta a un error en mi diseño. Pero, estoy atascado. Realmente apreciaría algo de ayuda para resolver esto. Aquí está el desafío ...

Quiero crear una clase base abstracta (llamémosle Fruit) que encapsula un código de inicialización complejo. Este código no se puede colocar en el constructor, ya que parte de él se basará en llamadas a métodos virtuales.

La fruta será heredada por otras clases concretas (Apple, Orange), cada una de las cuales debe exponer un método de fábrica estándar CreateInstance () para crear e inicializar una instancia.

Si la herencia de miembros estáticos fuera factible, colocaría el método de fábrica en la clase base y usaría una llamada de método virtual a la clase derivada para obtener el tipo desde el cual se debe inicializar una instancia concreta. El código del cliente invocaría simplemente a Apple.CreateInstance () para obtener una instancia de Apple totalmente inicializada.

Pero claramente esto no es posible, por lo que alguien puede explicar cómo mi diseño debe cambiar para adaptarse a la misma funcionalidad.


74
2017-09-04 15:53


origen


Respuestas:


Una idea:

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

Y llame así:

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

No se necesitan clases de fábrica adicionales.


52
2017-09-04 16:08



Mueva el método de fábrica del tipo y colóquelo en su propia clase de fábrica.

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

Pero, como estoy viendo esto, no hay necesidad de mover el método Create a su propia clase Factory (aunque creo que es preferible -separación de inquietudes-), puedes ponerlo en la clase Fruit:

public abstract class Fruit
{

   public abstract string Define();

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

}

Y, para ver si funciona:

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

            Console.ReadLine ();
        }        
    }

15
2017-09-04 15:59



¿Por qué no crear una clase de fábrica (con plantilla) con un método de creación?

FruitFactory<Banana>.Create();

4
2017-09-04 15:59



Haría algo como esto

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

4
2017-09-04 16:01



los WebRequest clase y sus tipos derivados en .NET BCL representan un buen ejemplo de cómo este tipo de diseño se puede implementar relativamente bien.

los WebRequest la clase tiene varias subclases, incluyendo HttpWebRequest y FtpWebReuest. Ahora esto WebRequest la clase base también es un tipo de fábrica y expone una estática Create método (los constructores de instancias están ocultos, según lo requerido por el patrón de fábrica).

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

Esta Create método devuelve una implementación específica de la WebRequest clase, y utiliza el URI (o cadena URI) para determinar el tipo de objeto para crear y devolver.

Esto tiene el resultado final del siguiente patrón de uso:

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

Personalmente, creo que esta es una buena forma de abordar el problema, y ​​de hecho parece ser el método preferido de los creadores de .NET Framework.


3
2017-09-04 16:03



En primer lugar, el hecho de no tener inicializadores estáticos que puedan ser virtuales no significa que no pueda tener métodos de miembros "estándar", que podrían estar sobrecargados. En segundo lugar, puede llamar a sus métodos virtuales desde los constructores, y funcionarán como se esperaba, por lo que no hay problema aquí. En tercer lugar, puede usar genéricos para tener una fábrica segura para tipos.
Aquí hay un código que usa el método Factoryize + initialize () que es llamado por el constructor (y está protegido, por lo que no tiene que preocuparse de que alguien lo vuelva a llamar después de crear un objeto):


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

3
2017-09-04 16:06



Diría que lo mejor que se puede hacer es crear un método de inicialización virtual / abstracto en la clase de fruta que se debe llamar y luego crear una clase externa de 'fábrica de fruta' para crear instancias:


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

0
2017-09-04 15:59