2009-09-04 11 views
68

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".¿Cuál es la alternativa correcta a la herencia de métodos estáticos?

OK, 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 complejo de inicialización. 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.

+1

Me gustaría señalar que la frase "Este código no se puede colocar en el constructor, ya que parte de ella se basará en llamadas a métodos virtuales" no es correcta. PUEDE llamar a métodos virtuales dentro de un constructor. (Tendrá que diseñar sus llamadas cuidadosamente, porque puede tener una clase parcialmente inicializada cuando se llaman los métodos virtuales, pero es compatible). Consulte la respuesta de Ravadre para obtener más detalles. – Ruben

Respuesta

52

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 llama así:

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

No hay clases de fábrica adicionales necesarios.

+8

En mi humilde opinión, es mejor mover el tipo genérico al método CreateInstance en lugar de ponerlo en el nivel de clase. (Como lo hice en mi respuesta). –

+1

Su preferencia es notada. Una breve explicación de su preferencia sería más apreciada. –

+6

Al hacerlo, no 'contaminarás' al resto de la clase; el parámetro tipo solo es necesario en el método Create (en este ejemplo al menos). Además de eso, creo que: Fruit.Create (); es más legible luego: Fruta .Crear(); –

0

yo diría que lo mejor que puede hacer es crear un método de iniciación del abstracto/virtual en la clase de fruta que debe ser llamado y luego crear una clase externa 'fábrica de frutas' 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; 
    } 
} 
4

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

FruitFactory<Banana>.Create(); 
+0

Te derrota. ;) –

15

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 de creación de su propia clase de fábrica (aunque creo que es preferible -La separación de Preocupaciones-), se puede poner en la clase de frutas:

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

Tu código no se compilará. necesitas la cláusula donde T: new(). Sin embargo, este es un engaño exacto mío. –

+1

Pruébalo, compila. –

+1

Aunque se podría crear una clase estática de FruitFactory, preferiría una interfaz de IFruitFactory, junto con una clase de FruitFactory instanciable. Puede terminar con una sola clase de FruitFactory, cuyo método CreateFruit no hace referencia a su instancia de objeto y, por lo tanto, podría haber sido un método estático, pero si alguna vez resulta necesario ofrecer múltiples formas de crear fruta, o si la creación de fruta alguna vez requiere la capacidad de mantener el estado, el uso de métodos de instancia puede resultar útil. – supercat

4

me gustaría hacer 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() 
3

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

La clase WebRequest tiene varias sub-clases, incluyendo HttpWebRequest y FtpWebReuest. Ahora, esta clase base WebRequest también es un tipo de fábrica y expone un método estático Create (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) 

Este método Create devuelve una implementación específica de la clase WebRequest, 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/"); 

Yo personalmente creo que esto es una buena manera de abordar el problema, y ​​en efecto, parece ser el método deseado de .NET Framework creadores.

3

En primer lugar, 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 utiliza el método miembro de la fábrica + Initialize() que es llamado por el constructor (y que está protegido, por lo que no tiene que preocuparse, que alguien va a llamar de nuevo después de la creación de 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()); 
    } 
} 
+1

En general, es mejor evitar llamar a los métodos virtuales de los constructores. Ver http://blogs.msdn.com/abhinaba/archive/2006/02/28/540357.aspx – TrueWill

+0

Es cierto, puede ser complicado, aunque es posible y siempre funcionará como debería, el problema es que para entender lo que significa 'debería'. En general, tiendo a evitar llamar a métodos más complicados (incluso no virtuales, es decir, porque a menudo se construyen de una manera que les permite lanzar una excepción, y no me gusta que mis ctors arrojen algo) , pero esa es la decisión del desarrollador. –

Cuestiones relacionadas