Qual è la corretta alternativa al metodo statico eredità?

Ho capito che il metodo statico l’ereditarietà non è supportato in C#. Ho anche letto un certo numero di discussioni (anche qui) in cui gli sviluppatori affermano bisogno di questa funzionalità, per cui la risposta tipica è: “se hai bisogno di membro statico eredità, c’è un difetto nella progettazione”.

OK, dato che l’OOP non mi voglio nemmeno pensare statico eredità, devo concludere che la mia apparente bisogno di punti per un errore nel mio design. Ma, mi sono bloccato. Vorrei davvero apprezzare un po ‘ di aiuto per risolvere questo. Ecco la sfida …

Voglio creare una classe base astratta (chiamiamolo un Frutto), che racchiude in sé alcuni complessi del codice di inizializzazione. Questo codice non può essere inserito nella funzione di costruzione, dal momento che alcuni di esso si basa sul metodo virtuale chiamate.

Frutta verrà ereditato da altre classi concrete (Mela, Arancia), ciascuno dei quali deve esporre un standard di fabbrica del metodo CreateInstance() per creare e inizializzare un’istanza.

Se il membro statico eredità fosse fattibile, vorrei porre la fabbrica di metodo nella classe base e utilizzare un metodo virtuale chiamata alla classe derivata, per ottenere il tipo da cui concreta istanza deve essere inizializzato. Il codice del client sarebbe semplice richiamare Apple.CreateInstance() per ottenere una cucina completamente inizializzato Apple istanza.

Ma chiaramente questo non è possibile, quindi per favore qualcuno può spiegare come le mie esigenze di design di cambiare per accogliere la stessa funzionalità.

  • Vorrei sottolineare che l’affermazione “Questo codice non può essere inserito nella funzione di costruzione, dal momento che alcuni di esso si basa sul metodo virtuale chiamate” non è corretto. È POSSIBILE chiamare i metodi virtuali all’interno di un costruttore. (È possibile progettare le vostre chiamate con attenzione, perché si può avere una parziale inizializzato classe quando i metodi virtuali sono chiamati, ma è supportato.) Vedere Ravadre risposta per ulteriori dettagli.

 

7 Replies
  1. 57

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

    E chiamata in questo modo:

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

    Senza supplemento di fabbrica le classi necessarie.

    • IMHO, è meglio spostare il tipo generico per il metodo CreateInstance invece di mettere il livello di classe. (Come ho fatto io nella mia risposta).
    • La vostra preferenza è notato. Una breve spiegazione della vostra preferenza sarebbe più apprezzato.
    • Così facendo, non ‘inquinare’ il resto della classe; il tipo di parametro è necessaria solo nel metodo Create (in questo esempio, almeno). Accanto a questo, io penso che: la Frutta.Creare<Apple>(); è più leggibile, quindi: Frutta<Apple>.Create();
    • Vedo che, anche se il “inquinamento” problema è fortemente dipendente dalle esigenze del resto della classe (cioè, il tipo di informazioni possono essere necessari altrove). Per le finalità e i limiti di questo esempio, sia la soluzione sembra più che sufficiente.
    • Non si dovrebbe fare il costruttore privato?
    • Sì, buona cattura. Ci dovrebbe essere un costruttore definito in Apple il cui ambito è definito come non-pubblico, che al di fuori delle classi deve utilizzare il metodo factory per creare l’oggetto. Si potrebbe aver bisogno di essere protetti in modo tale che il Frutto di classe possono accedere. La frutta non tecnicamente bisogno di un costruttore in quanto è una classe astratta. Mi si modifica di conseguenza.
    • Non si possono fare Apple costruttore a meno di pubblico, perché where T : Fruit<T>, new() dettami che T deve avere un costruttore pubblico. @Matt Hamsmith – Il codice non viene compilato fino a quando si rimuove protected Apple() { } . Ho già provato in VS.
    • Si sono corretti. Ho modificato. Questo esporre la Apple ad essere costruito senza l’utilizzo di Frutta CreateInstance metodo factory statico, ma che sembra inevitabile.
    • Che cosa succede se ho bisogno di passare dei parametri CreateInstance, ma i parametri necessari potrebbero differire in classe ereditata?
    • L’OP stati “Frutta verrà ereditato da altre classi concrete (Mela, Arancia), ciascuno dei quali deve esporre un standard di fabbrica del metodo CreateInstance() per creare e inizializzare un’istanza.” Sembra che la tua domanda si riferisce alla non-standard CreateInstance() metodi per inizializzare un’istanza. La vostra situazione potrebbe giustificare una completamente diversa domanda e la soluzione.
    • Vorrei anche sottolineare che questo può essere chiamato semplicemente Apple.CreateInstance() invece di Fruit<Apple>.CreateInstance()
    • L’unico vero vantaggio di questo disegno è che si può chiamare Apple.CreateInstance(). Altrimenti perché non solo public static T CreateInstance<T>() where T : Fruit e di chiamare le cose come Fruit.CreateInstance<Apple>()? Mi hanno suggerito di progettazione prima e, infine, si fermò perché si traduce in un sacco di confusione codice. E questo non è solo il mio parere: blogs.msdn.microsoft.com/ericlippert/2011/02/03/…

  2. 16

    Spostare la fabbrica metodo del tipo, e metterlo in una sua Fabbrica di classe.

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

    Ma, come vedo in questo, non c’è bisogno di spostare il metodo Create per il proprio Stabilimento di classe (anche se penso che sia preferibile -la separazione delle preoccupazioni-), si può mettere a Frutto classe:

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

    E, per vedere se funziona:

        class Program
        {
            static void Main( string[] args )
            {
                Console.WriteLine (Fruit.CreateFruit<Apple> ().Define ());
                Console.WriteLine (Fruit.CreateFruit<Orange> ().Define ());
    
                Console.ReadLine ();
            }        
        }
    • Il codice non viene compilato. hai bisogno di where T : new() clausola. Tuttavia, questo è un esatto vittima delle mine.
    • Provare, compila.
    • Anche se si potrebbe creare una statico FruitFactory classe, mi andrebbe bene anche un IFruitFactory di interfaccia, con un’istanze FruitFactory classe. Si può finire con un solo FruitFactory di classe, la cui CreateFruit metodo non fa alcun riferimento alla sua istanza di oggetto e quindi potrebbe anche essere stato un metodo statico, ma se mai diventa necessario offrire diversi modi per la creazione di frutta, o se frutto di creazione di sempre richiede la capacità di mantenere lo stato, utilizzando i metodi di istanza può essere utile.
  3. 4

    Perché non creare una classe factory (stampo) con un metodo di creazione?

    FruitFactory<Banana>.Create();
    • Battuto ad esso. 😉
  4. 4

    Vorrei fare qualcosa di simile a questo

     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. 3

    Il WebRequest classe e dei suoi derivati tipi .NET BCL rappresentano un buon esempio di come questo tipo di struttura può essere realizzata abbastanza bene.

    Il WebRequest classe ha diverse sotto-classi, tra cui HttpWebRequest e FtpWebReuest. Ora, questo WebRequest classe base è anche un tipo di fabbrica, ed espone una statico Creare metodo (i costruttori di istanza sono nascosti, come richiesto dal modello factory).

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

    Questo Create metodo restituisce una specifica implementazione di WebRequest classe, e utilizza la notazione URI (o stringa URI) per determinare il tipo di oggetto da creare e ritorno.

    Questo è il risultato finale delle seguenti modalità 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/");

    Io personalmente penso che questo è un buon modo per affrontare il problema, e in effetti sembrano essere il metodo preferito della .NET Framework creatori.

  6. 3

    Prima di tutto, non avendo inizializzatori statici che possono essere virtuale non significa che non si può avere “standard” metodi di membro, che potrebbe essere sovraccarico. In secondo luogo di tutti, è possibile chiamare i metodi virtuali da costruttori e funzionano come previsto, quindi non c’è nessun problema qui. Terzo di tutti, È possibile utilizzare farmaci generici avere tipo-cassetta di sicurezza in fabbrica.

    Ecco un po ‘ di codice che utilizza fabbrica + membro Initialize() metodo che viene chiamato dal costruttore (ed è protetta, in modo da non devono preoccuparsi, che qualcuno chiama ancora dopo la creazione di un oggetto):

    
    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());
        }
    }
    • In genere è meglio evitare di chiamare i metodi virtuali da costruttori. Vedere blogs.msdn.com/abhinaba/archive/2006/02/28/540357.aspx
    • È vero, si può essere difficile, anche se è possibile e funziona sempre come dovrebbe, il problema è capire che cosa significa ‘dovrebbe’. In generale, tendo a evitare di chiamare i metodi più complessi (anche non virtuale, vale a dire. perché sono spesso costruiti in un modo che permette loro di lanciare un’eccezione, e non mi piace il mio ctors buttare nulla), ma che lo sviluppatore di decisione.
  7. 0

    Direi che la cosa migliore da fare è creare un virtual/abstract Inizializzare metodo di frutta e di classe, che deve essere chiamato e quindi creare un esterno ‘frutto di fabbrica’ di classe 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;
        }
    }

Lascia un commento