Qual è l’alternativa corretta all’ereditarietà del metodo statico?

Comprendo che l’ereditarietà del metodo statico non è supportata in C #. Ho anche letto una serie di discussioni (anche qui) in cui gli sviluppatori sostengono la necessità di questa funzionalità, a cui la risposta tipica è “se hai bisogno di ereditarietà statica dei membri, c’è un difetto nella progettazione”.

OK, dato che l’OOP non vuole che io pensi nemmeno all’eredità statica, devo concludere che il mio apparente bisogno di farlo indica un errore nel mio progetto. Ma sono bloccato. Gradirei davvero un po ‘d’aiuto risolvere questo. Ecco la sfida …

Voglio creare una class base astratta (chiamiamola un frutto) che incapsula un codice di inizializzazione complesso. Questo codice non può essere inserito nel costruttore, poiché alcuni di essi si baseranno su chiamate di metodi virtuali.

La frutta sarà ereditata da altre classi concrete (Apple, Orange), ognuna delle quali deve esporre un metodo factory standard CreateInstance () per creare e inizializzare un’istanza.

Se l’ereditarietà dei membri statici fosse fattibile, posizionerei il metodo factory nella class base e userei una chiamata al metodo virtuale alla class derivata per ottenere il tipo da cui deve essere inizializzata un’istanza concreta. Il codice client invocerebbe semplicemente Apple.CreateInstance () per ottenere un’istanza Apple completamente inizializzata.

Ma chiaramente questo non è ansible, quindi qualcuno può spiegare come il mio progetto deve cambiare per adattarsi alla stessa funzionalità.

Un’idea:

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

E chiama così:

 Apple myAppleVar = Fruit.CreateInstance(); 

Non sono necessarie classi di produzione aggiuntive.

Sposta il metodo factory fuori dal tipo e mettilo nella sua class 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 { public static T CreateFruit() where T : Fruit, new() { return new T(); } } 

Ma, mentre guardo questo, non c’è bisogno di spostare il metodo Create nella sua class Factory (anche se penso che sia preferibile -separazione di preoccupazioni-), puoi metterlo nella class Fruit:

 public abstract class Fruit { public abstract string Define(); public static T CreateFruit() where T : Fruit, new() { return new T(); } } 

E, per vedere se funziona:

  class Program { static void Main( string[] args ) { Console.WriteLine (Fruit.CreateFruit ().Define ()); Console.WriteLine (Fruit.CreateFruit ().Define ()); Console.ReadLine (); } } 

Perché non creare una class factory (basata su modelli) con un metodo di creazione?

 FruitFactory.Create(); 

Farei qualcosa di simile

  public abstract class Fruit() { public abstract void Initialize(); } public class Apple() : Fruit { public override void Initialize() { } } public class FruitFactory where T : Fruit, new { public static  CreateInstance() { T fruit = new T(); fruit.Initialize(); return fruit; } } var fruit = FruitFactory.CreateInstance() 

La class WebRequest e i suoi tipi derivati ​​in .NET BCL rappresentano un buon esempio di come questo tipo di progettazione possa essere implementato relativamente bene.

La class WebRequest ha diverse sottoclassi, tra cui HttpWebRequest e FtpWebReuest . Ora, questa class base WebRequest è anche un tipo factory e espone un metodo Create statico (i costruttori di istanze sono nascosti, come richiesto dal pattern factory).

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

Questo metodo Create restituisce un’implementazione specifica della class WebRequest e utilizza l’URI (o la stringa URI) per determinare il tipo di object da creare e restituire.

Questo ha il risultato finale del seguente schema di utilizzo:

 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 ritengo che questo sia un buon modo per affrontare il problema, e in effetti sembra essere il metodo preffered dei creatori di .NET Framework.

Prima di tutto, non avere inizializzatori statici che possono essere virtuali non significa che non si possano avere metodi membri “standard”, che potrebbero essere sovraccarichi. In secondo luogo, puoi chiamare i tuoi metodi virtuali dai costruttori, e funzioneranno come previsto, quindi non ci sono problemi qui. In terzo luogo, è ansible utilizzare i generici per avere una fabbrica sicura del tipo.
Ecco un codice che utilizza il metodo Initialize () factory + member richiamato dal costruttore (ed è protetto, quindi non devi preoccuparti che qualcuno lo richiamerà dopo aver creato un object):

 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() where T : Fruit, new() { return new T(); } } public class Program { static void Main() { Apple apple = FruitFactory.CreateFruit(); Console.WriteLine(apple.ToString()); Orange orange = new Orange(); Console.WriteLine(orange.ToString()); Fruit appleFruit = FruitFactory.CreateFruit(); Console.WriteLine(appleFruit.ToString()); } } 

Direi che la cosa migliore da fare è creare un metodo Initialise virtuale / astratto sulla class fruit che deve essere chiamato e quindi creare una class esterna ‘fruit factory’ per creare istanze:

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