Domanda

Immagina una classe base con molti costruttori e un metodo virtuale

public class Foo
{
   ...
   public Foo() {...}
   public Foo(int i) {...}
   ...
   public virtual void SomethingElse() {...}
   ...
}

e ora voglio creare una classe discendente che sovrascrive il metodo virtuale:

public class Bar : Foo 
{
   public override void SomethingElse() {...}
}

E un altro discendente che fa altre cose:

public class Bah : Bar
{
   public void DoMoreStuff() {...}
}

Devo davvero copiare tutti i costruttori da Foo a Bar e Bah? E poi se cambio una firma del costruttore in Foo, devo aggiornarla in Bar e Bah?

Non c'è modo di ereditare i costruttori? Non c'è modo di incoraggiare il riutilizzo del codice?

È stato utile?

Soluzione

Sì, dovrai implementare i costruttori che hanno senso per ogni derivazione e quindi utilizzare la parola chiave base per indirizzare quel costruttore alla classe base appropriata o al this parola chiave per indirizzare un costruttore a un altro costruttore della stessa classe.

Se il compilatore avesse formulato ipotesi sull'ereditarietà dei costruttori, non saremmo in grado di determinare correttamente come sono stati istanziati i nostri oggetti. Nella maggior parte dei casi, dovresti considerare perché hai così tanti costruttori e considerare di ridurli a uno o due solo nella classe base. Le classi derivate possono quindi mascherarne alcune usando valori costanti come null ed esporre solo quelle necessarie attraverso i loro costruttori.

Aggiornamento

In C # 4 è possibile specificare i valori dei parametri predefiniti e utilizzare i parametri con nome per fare in modo che un singolo costruttore supporti configurazioni di argomenti multipli anziché avere un costruttore per configurazione.

Altri suggerimenti

387 costruttori ?? Questo è il tuo problema principale. Che ne dici invece di questo?

public Foo(params int[] list) {...}

Sì, devi copiare tutti i 387 costruttori. Puoi riutilizzare alcuni reindirizzandoli:

  public Bar(int i): base(i) {}
  public Bar(int i, int j) : base(i, j) {}

ma è il massimo che puoi fare.

Peccato che siamo costretti a dire al compilatore l'ovvio:

Subclass(): base() {}
Subclass(int x): base(x) {}
Subclass(int x,y): base(x,y) {}

Devo solo fare 3 costruttori in 12 sottoclassi, quindi non è un grosso problema, ma non mi piace molto ripeterlo su ogni sottoclasse, dopo essere stato abituato a non doverlo scrivere per così tanto tempo. Sono sicuro che ci sia una ragione valida per questo, ma non credo di aver mai riscontrato un problema che richiede questo tipo di restrizione.

Non dimenticare che puoi anche reindirizzare i costruttori ad altri costruttori allo stesso livello di eredità:

public Bar(int i, int j) : this(i) { ... }
                            ^^^^^

Un'altra soluzione semplice potrebbe essere quella di utilizzare una struttura o una semplice classe di dati che contenga i parametri come proprietà; in questo modo puoi avere tutti i valori e i comportamenti predefiniti impostati in anticipo, passando la classe di parametri "quot" in come parametro del singolo costruttore:

public class FooParams
{
    public int Size...
    protected myCustomStruct _ReasonForLife ...
}
public class Foo
{
    private FooParams _myParams;
    public Foo(FooParams myParams)
    {
          _myParams = myParams;
    }
}

Questo evita il caos di più costruttori (a volte) e offre una tipizzazione forte, valori predefiniti e altri vantaggi non forniti da un array di parametri. Inoltre, è facile da portare avanti poiché qualsiasi cosa eredita da Foo può ancora raggiungere o addirittura aggiungere FooParams secondo necessità. Devi ancora copiare il costruttore, ma hai sempre (il più delle volte) solo (come regola generale) mai (almeno, per ora) bisogno di un costruttore.

public class Bar : Foo
{
    public Bar(FooParams myParams) : base(myParams) {}
}

Mi piacciono molto gli approcci sovraccaricati di Initailize () e Class Factory Pattern, ma a volte devi solo avere un costruttore intelligente. Solo un pensiero.

Dato che Foo è una classe, non puoi creare metodi di Initialise () sovraccarichi virtuali? Quindi sarebbero disponibili per le sottoclassi e sarebbero ancora estensibili?

public class Foo
{
   ...
   public Foo() {...}

   public virtual void Initialise(int i) {...}
   public virtual void Initialise(int i, int i) {...}
   public virtual void Initialise(int i, int i, int i) {...}
   ... 
   public virtual void Initialise(int i, int i, ..., int i) {...}

   ...

   public virtual void SomethingElse() {...}
   ...
}

Questo non dovrebbe avere un costo di prestazione più elevato a meno che tu non abbia molti valori di proprietà predefiniti e lo colpisca molto.

public class BaseClass
{
    public BaseClass(params int[] parameters)
    {

    }   
}

public class ChildClass : BaseClass
{
    public ChildClass(params int[] parameters)
        : base(parameters)
    {

    }
}

Personalmente penso che questo sia un errore da parte di Microsoft, avrebbero dovuto consentire al programmatore di sovrascrivere la visibilità di Costruttori, Metodi e Proprietà nelle classi base, e quindi fare in modo che i Costruttori siano sempre ereditati.

In questo modo semplicemente sovrascriviamo (con una visibilità inferiore, ad es. Privato) i costruttori che NON vogliamo invece di dover aggiungere tutti i costruttori che VOGLIAMO. Delphi lo fa in questo modo e mi manca.

Prendi ad esempio se vuoi sovrascrivere la classe System.IO.StreamWriter, devi aggiungere tutti e 7 i costruttori alla tua nuova classe e se ti piace commentare devi commentare ognuno con l'header XML. A peggiorare le cose, la vista dei metadati non inserisce i commenti XML come commenti XML corretti, quindi dobbiamo procedere riga per riga e copiarli e incollarli. Cosa stava pensando Microsoft qui?

Ho effettivamente scritto una piccola utility in cui è possibile incollare il codice dei metadati e lo convertirà in commenti XML utilizzando la visibilità nascosta.

  

Devo davvero copiare tutti i costruttori da Foo a Bar e Bah ? E poi se cambio una firma del costruttore in Foo , devo aggiornarla in Bar e Bah ?

Sì, se usi i costruttori per creare istanze.

  

Non c'è modo di ereditare i costruttori?

No.

  

Non c'è modo di incoraggiare il riutilizzo del codice?

Bene, non capirò se ereditare i costruttori sarebbe una cosa buona o cattiva e se incoraggerebbe il riutilizzo del codice, dal momento che non li abbiamo e non li prenderemo. : -)

Ma qui nel 2014, con l'attuale C #, puoi ottenere qualcosa di molto come costruttori ereditati usando invece un metodo generico create . Può essere uno strumento utile da avere nella cintura, ma non lo raggiungeresti alla leggera. L'ho preso di recente di fronte alla necessità di passare qualcosa nel costruttore di un tipo di base usato in un paio di centinaia di classi derivate (fino a poco tempo fa la base non aveva bisogno di argomenti, quindi il costruttore predefinito andava bene & nbsp; - il le classi derivate non dichiarano affatto i costruttori e ottengono quella fornita automaticamente).

Sembra così:

// In Foo:
public T create<T>(int i) where: where T : Foo, new() {
    T obj = new T();
    // Do whatever you would do with `i` in `Foo(i)` here, for instance,
    // if you save it as a data member;  `obj.dataMember = i;`
    return obj;
}

Ciò significa che puoi chiamare la generica funzione create usando un parametro type che è qualsiasi sottotipo di Foo che ha un costruttore a zero argomenti.

Quindi, invece di fare Bar b new Bar (42) , dovresti farlo:

var b = Foo.create<Bar>(42);
// or
Bar b = Foo.create<Bar>(42);
// or
var b = Bar.create<Bar>(42); // But you still need the <Bar> bit
// or
Bar b = Bar.create<Bar>(42);

Lì ho mostrato che il metodo create si trova direttamente su Foo , ma ovviamente potrebbe trovarsi in una classe di fabbrica di qualche tipo, se le informazioni sono impostate può essere impostato da quella classe di fabbrica.

Solo per chiarezza: il nome create non è importante, potrebbe essere makeThingy o qualsiasi altra cosa ti piaccia.

Esempio completo

using System.IO;
using System;

class Program
{
    static void Main()
    {
        Bar b1 = Foo.create<Bar>(42);
        b1.ShowDataMember("b1");

        Bar b2 = Bar.create<Bar>(43); // Just to show `Foo.create` vs. `Bar.create` doesn't matter
        b2.ShowDataMember("b2");
    }

    class Foo
    {
        public int DataMember { get; private set; }

        public static T create<T>(int i) where T: Foo, new()
        {
            T obj = new T();
            obj.DataMember = i;
            return obj;
        }
    }

    class Bar : Foo
    {
        public void ShowDataMember(string prefix)
        {
            Console.WriteLine(prefix + ".DataMember = " + this.DataMember);
        }
    }
}

Il problema non è che Bar e Bah devono copiare 387 costruttori, il problema è che Foo ha 387 costruttori. Foo fa chiaramente troppe cose - refactor veloce! Inoltre, a meno che tu non abbia una buona ragione per avere valori impostati nel costruttore (che, se fornisci un costruttore senza parametri, probabilmente non lo fai), ti consiglio di usare la proprietà ottenere / impostazione.

No, non è necessario copiare tutti i 387 costruttori su Bar e Bah. Bar e Bah possono avere tanti costruttori quanti ne vuoi, indipendentemente da quanti ne definisci su Foo. Ad esempio, potresti scegliere di avere un solo costruttore di barre che costruisce Foo con il 212 ° costruttore di Foo.

Sì, tutti i costruttori che cambi in Foo da cui dipendono Bar o Bah richiederanno di modificare Bar e Bah di conseguenza.

No, in .NET non è possibile ereditare i costruttori. Ma puoi ottenere il riutilizzo del codice chiamando il costruttore di una classe base all'interno del costruttore della sottoclasse o chiamando un metodo virtuale che definisci (come Initialize ()).

Potresti essere in grado di adattare una versione di Idioma del costruttore virtuale C ++ . Per quanto ne so, C # non supporta i tipi di ritorno covarianti. Credo che sia nella lista dei desideri di molte persone.

Troppi costruttori sono un segno di un design rotto. Meglio una classe con pochi costruttori e la capacità di impostare proprietà. Se hai davvero bisogno del controllo sulle proprietà, considera una factory nello stesso spazio dei nomi e rendi i setter delle proprietà interni. Lascia che la fabbrica decida come creare un'istanza della classe e impostarne le proprietà. La fabbrica può avere metodi che accettano tutti i parametri necessari per configurare correttamente l'oggetto.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top