Sottoclassi di soli costruttori: si tratta di un anti-schema?


37

Stavo discutendo con un collega e alla fine abbiamo avuto intuizioni contrastanti sullo scopo della sottoclasse. La mia intuizione è che se una funzione primaria di una sottoclasse è esprimere un intervallo limitato di possibili valori del suo genitore, allora probabilmente non dovrebbe essere una sottoclasse. Ha sostenuto l'intuizione opposta: quella sottoclasse rappresenta un oggetto più "specifico", e quindi una relazione di sottoclasse è più appropriata.

Per rendere più concreta la mia intuizione, penso che se ho una sottoclasse che estende una classe genitore ma l'unico codice che sovrascrive la sottoclasse è un costruttore (sì, so che i costruttori generalmente non "sovrascrivono", sopporta con me), quindi ciò che era veramente necessario era un metodo di aiuto.

Ad esempio, considera questa classe in qualche modo reale:

public class DataHelperBuilder
{
    public string DatabaseEngine { get; set; }
    public string ConnectionString { get; set; }

    public DataHelperBuilder(string databaseEngine, string connectionString)
    {
        DatabaseEngine = databaseEngine;
        ConnectionString = connectionString;
    }

    // Other optional "DataHelper" configuration settings omitted

    public DataHelper CreateDataHelper()
    {
        Type dataHelperType = DatabaseEngineTypeHelper.GetType(DatabaseEngine);
        DataHelper dh = (DataHelper)Activator.CreateInstance(dataHelperType);
        dh.SetConnectionString(ConnectionString);

        // Omitted some code that applies decorators to the returned object
        // based on omitted configuration settings

        return dh;
    }
}

La sua tesi è che sarebbe del tutto appropriato avere una sottoclasse come questa:

public class SystemDataHelperBuilder
{
    public SystemDataHelperBuilder()
        : base(Configuration.GetSystemDatabaseEngine(),
               Configuration.GetSystemConnectionString())
    {
    }
 }

Quindi, la domanda:

  1. Tra le persone che parlano di modelli di design, quale di queste intuizioni è corretta? La sottoclasse come descritto sopra è un anti-pattern?
  2. Se è un anti-pattern, come si chiama?

Mi scuso se questa è stata una risposta facilmente googleable; le mie ricerche su google hanno per lo più restituito informazioni sull'anti-modello del costruttore telescopico e non proprio quello che stavo cercando.


2
Considero la parola "aiutante" in un nome un antipattern. È come leggere un saggio con riferimenti costanti a una cosa e una cosa del genere. Di 'quello che è . È una fabbrica? Costruttore? Per me sa di un processo di pensiero pigro e incoraggia la violazione del principio della singola responsabilità, dal momento che un nome semantico appropriato lo dirige. Sono d'accordo con gli altri elettori e dovresti accettare la risposta di @lxrec. La creazione di una sottoclasse per un metodo factory è completamente inutile, a meno che non ci si possa fidare degli utenti per passare gli argomenti corretti come specificato nella documentazione. In tal caso, ottenere nuovi utenti.
Aaron Hall,

Sono assolutamente d'accordo con la risposta di @ Ixrec. Dopotutto, la sua risposta dice che avevo sempre ragione e che il mio collega aveva torto. ;-) Ma sul serio, è esattamente per questo che mi sono sentito strano ad accettarlo; Pensavo di poter essere accusato di parzialità. Ma sono nuovo per i programmatori StackExchange; Sarei disposto ad accettarlo se mi fosse assicurato che non sarebbe una violazione dell'etichetta.
HardlyKnowEm

1
No, ci si aspetta che tu accetti la risposta che ritieni più preziosa. Quello che la comunità ritiene finora più prezioso è di gran lunga quello di Ixrec più di 3 a 1. Quindi si aspetterebbero che tu lo accetti. In questa comunità, c'è una frustrazione molto piccola espressa in un interrogante che accetta la risposta sbagliata, ma c'è una grande frustrazione espressa in interrogatori che non accettano mai una risposta. Si noti che nessuno si lamenta della risposta accettata qui, invece si lamentano della votazione, che sembra raddrizzare se stessa: stackoverflow.com/q/2052390/541136
Aaron Hall

Molto bene, lo accetterò. Grazie per aver dedicato del tempo per educarmi sugli standard della comunità qui.
HardlyKnowEm

Risposte:


54

Se tutto ciò che vuoi fare è creare la classe X con determinati argomenti, la sottoclasse è un modo strano di esprimere quell'intento, perché non stai usando nessuna delle funzionalità che le classi e l'eredità ti danno. Non è proprio un anti-pattern, è solo strano e un po 'inutile (a meno che tu non abbia altri motivi per farlo). Un modo più naturale di esprimere questo intento sarebbe un Metodo di Fabbrica , che in questo caso è un nome di fantasia per il tuo "metodo di supporto".

Per quanto riguarda l'intuizione generale, sia "più specifico" che "un intervallo limitato" sono modi potenzialmente dannosi di pensare alle sottoclassi, perché entrambi implicano che rendere Square una sottoclasse di Rectangle sia una buona idea. Senza fare affidamento su qualcosa di formale come LSP, direi che un'intuizione migliore è che una sottoclasse fornisce un'implementazione dell'interfaccia di base o estende l'interfaccia per aggiungere alcune nuove funzionalità.


2
I metodi di fabbrica, tuttavia, non consentono di applicare determinati comportamenti (governati da argomenti) nella base di codice. E a volte è utile annotare esplicitamente che questa classe / metodo / campo prende SystemDataHelperBuilderspecificamente.
Telastyn,

3
Ah, l'argomento quadrato contro rettangolo era esattamente quello che stavo cercando, credo. Grazie!
HardlyKnowEm,

7
Ovviamente, avere quadrato come sottoclasse di rettangolo può andare bene se sono immutabili. Devi davvero guardare a LSP.
David Conrad,

10
Il problema del "problema" quadrato / rettangolo è che le forme geometriche sono immutabili. Puoi sempre usare un quadrato ovunque abbia senso un rettangolo, ma il ridimensionamento non è qualcosa che fanno i quadrati o i rettangoli.
Doval,

3
@nha LSP = Principio di sostituzione di Liskov
Ixrec,

16

Quale di queste intuizioni è corretta?

Il tuo collega è corretto (presupponendo sistemi di tipo standard).

Pensaci, le classi rappresentano possibili valori legali. Se la classe Aha un campo byte F, potresti essere propenso a pensare che Aabbia 256 valori legali, ma quell'intuizione non è corretta. Asta limitando "ogni permutazione di valori mai" a "deve avere un campo Fcompreso tra 0 e 255".

Se si estende Acon il Bquale ha un altro campo byte FF, si aggiungono restrizioni . Invece di "valore dove Fè byte", hai "valore dove Fè byte && FFè anche byte". Dato che stai mantenendo le vecchie regole, tutto ciò che ha funzionato per il tipo di base funziona ancora per il tuo sottotipo. Ma dal momento che stai aggiungendo regole, ti stai ulteriormente specializzando "potrebbe essere qualsiasi cosa".

O, per pensare ad un altro modo, si supponga che Aha avuto un po 'di sottotipi: B, C, e D. Una variabile di tipo Apuò essere uno di questi sottotipi, ma una variabile di tipo Bè più specifica. (Nella maggior parte dei sistemi di tipo) non può essere a Co a D.

È un anti-pattern?

Enh? Avere oggetti specializzati è utile e non chiaramente dannoso per il codice. Raggiungere quel risultato usando il sottotipo è forse un po 'discutibile, ma dipende da quali strumenti hai a tua disposizione. Anche con strumenti migliori questa implementazione è semplice, diretta e robusta.

Non lo considero un anti-schema poiché non è chiaramente sbagliato e negativo in nessuna situazione.


5
"Più regole significano meno valori possibili, significa più specifici." Davvero non lo seguo. Il tipo byte and byteha sicuramente più valori del semplice tipo byte; 256 volte di più. A parte questo, non penso che tu possa confondere le regole con il numero di elementi (o cardinalità se vuoi essere preciso). Si rompe quando si considerano insiemi infiniti. Ci sono tanti numeri pari quanti sono numeri interi, ma sapere che un numero è pari è ancora una restrizione / mi dà più informazioni che sapere solo che è un numero intero! Lo stesso si può dire per i numeri naturali.
Doval,

6
@Telastyn Stiamo diventando fuori tema, ma è dimostrabile che la cardinalità (vagamente, "dimensione") dell'insieme degli interi pari sia uguale all'insieme di tutti gli interi, o anche a tutti i numeri razionali. È roba davvero bella. Vedi math.grinnell.edu/~miletijo/museum/infinite.html
HardlyKnowEm

2
La teoria degli insiemi non è abbastanza intuitiva. Puoi mettere numeri interi e dispari in una corrispondenza uno a uno (ad es. Usando la funzione n -> 2n). Questo non è sempre possibile; non è possibile mappare gli interi ai reali, quindi l'insieme dei reali è "più grande" degli interi. Ma puoi mappare l'intervallo (reale) [0, 1] sull'insieme di tutti i reali, in modo da renderli della stessa "dimensione". I sottoinsiemi sono definiti come "ogni elemento di Aè anche un elemento di B" proprio perché una volta che i tuoi set diventano infiniti, può succedere che siano la stessa cardinalità ma abbiano elementi diversi.
Doval,

1
Più correlato all'argomento in questione, l'esempio fornito è un vincolo che, in termini di programmazione orientata agli oggetti, estende effettivamente la funzionalità in termini di un campo aggiuntivo. Sto parlando di sottoclassi che non estendono la funzionalità, come il problema del cerchio-ellisse.
HardlyKnowEm,

1
@Doval: esiste più di un significato possibile di "meno", ovvero più di una relazione di ordinamento sui set. La relazione "è un sottoinsieme di" fornisce un ordinamento (parziale) ben definito sugli insiemi. Inoltre, "più regole" possono essere intese come "tutti gli elementi dell'insieme soddisfano più (cioè un superset di) proposizioni (da un certo insieme)". Con queste definizioni, "più regole" significa effettivamente "meno valori". Questo è, in termini molto approssimativi, l'approccio adottato da una serie di modelli semantici di programmi per computer, ad esempio domini Scott.
psmears,

12

No, non è un anti-pattern. Posso pensare a una serie di casi pratici per questo:

  1. Se si desidera utilizzare il controllo del tempo di compilazione per assicurarsi che le raccolte di oggetti siano conformi solo a una particolare sottoclasse. Ad esempio, se hai MySQLDaoe SqliteDaonel tuo sistema, ma per qualche motivo vuoi assicurarti che una raccolta contenga solo dati da una fonte, se usi sottoclassi come descrivi puoi far verificare al compilatore questa particolare correttezza. D'altra parte, se si memorizza l'origine dati come campo, questo diventerebbe un controllo di runtime.
  2. La mia attuale applicazione ha una relazione uno a uno tra AlgorithmConfiguration+ ProductClasse AlgorithmInstance. In altre parole, se si configura FooAlgorithmper ProductClassnel file delle proprietà, si può ottenere solo uno FooAlgorithmper quella classe di prodotto. L'unico modo per ottenere due FooAlgorithmsecondi per un dato ProductClassè creare una sottoclasse FooBarAlgorithm. Questo va bene, perché rende il file delle proprietà facile da usare (non è necessaria la sintassi per molte diverse configurazioni in una sezione del file delle proprietà), ed è molto raro che ci sia più di un'istanza per una data classe di prodotto.
  3. La risposta di @ Telastyn offre un altro buon esempio.

Bottom line: Non c'è davvero alcun danno nel fare questo. Un Anti-pattern è definito come:

Un anti-pattern (o antipattern) è una risposta comune a un problema ricorrente che di solito è inefficace e rischia di essere altamente controproducente.

Non c'è rischio di essere altamente controproducenti qui, quindi non è un anti-schema.


2
Sì, penso che se dovessi ripetere la frase, direi "odore di codice". Non è abbastanza male giustificare l'avvertimento della prossima generazione di giovani sviluppatori per evitarlo, ma è qualcosa che se lo vedi, può indicare che c'è un problema nel design generale, ma può anche essere corretto.
HardlyKnowEm,

Il punto 1 è giusto sul bersaglio. Non ho capito bene il punto 2, ma sono sicuro che è altrettanto buono ... :)
Phil

9

Se qualcosa è un pattern o un antipattern dipende in modo significativo dalla lingua e dall'ambiente in cui stai scrivendo. Ad esempio, i forloop sono un pattern in assembly, C e linguaggi simili e un anti-pattern in lisp.

Lascia che ti racconti una storia di alcuni codici che ho scritto molto tempo fa ...

Era in una lingua chiamata LPC e implementava un framework per lanciare incantesimi in un gioco. Avevi una superclasse di incantesimi, che aveva una sottoclasse di incantesimi di combattimento che gestiva alcuni controlli, e poi una sottoclasse per gli incantesimi di danno diretto a bersaglio singolo, che veniva quindi suddivisa in sottoclassi per i singoli incantesimi: missile magico, saetta e simili.

La natura di come funzionava LPC era che avevi un oggetto master che era un Singleton. prima di andare "eww Singleton" - era e non era affatto "eww". Uno non faceva copie degli incantesimi, ma piuttosto invocava i metodi (effettivamente) statici negli incantesimi. Il codice per il missile magico sembrava:

inherit "spells/direct";

void init() {
  ::init();
  damage = 10;
  cost = 3;
  delay = 1.0;
  caster_message = "You cast a magic missile at ${target}";
  target_message = "You are hit with a magic missile cast by ${caster}";
}

E lo vedi? È solo una classe di costruzione. Stabilì alcuni valori che erano nella sua classe astratta genitore (no, non dovrebbe usare setter - è immutabile) e basta. Ha funzionato bene . Era facile da codificare, facile da estendere e facile da capire.

Questo tipo di modello si traduce in altre situazioni in cui si dispone di una sottoclasse che estende una classe astratta e imposta alcuni valori propri, ma tutte le funzionalità si trovano nella classe astratta che eredita. Dai un'occhiata a StringBuilder in Java per un esempio di questo - non solo di costruzione, ma sono costretto a trovare molta logica nei metodi stessi (tutto è in AbstractStringBuilder).

Questa non è una brutta cosa, e certamente non è un anti-schema (e in alcune lingue potrebbe essere un modello stesso).


2

L'uso più comune di tali sottoclassi a cui riesco a pensare sono le gerarchie di eccezioni, sebbene si tratti di un caso degenerato in cui in genere non definiremmo nulla nella classe, purché il linguaggio ci consenta di ereditare i costruttori. Usiamo l'ereditarietà per esprimere che ReadErrorè un caso speciale di IOError, e così via, ma ReadErrornon è necessario ignorare alcun metodo di IOError.

Lo facciamo perché le eccezioni vengono rilevate verificandone il tipo. Pertanto, dobbiamo codificare la specializzazione nel tipo in modo che qualcuno possa catturare solo ReadErrorsenza catturare tutto IOError, se lo desidera.

Normalmente, controllare il tipo di cose è terribilmente male e cerchiamo di evitarlo. Se riusciamo a evitarlo, non è necessario esprimere le specializzazioni nel sistema dei tipi.

Potrebbe essere comunque utile, ad esempio se il nome del tipo appare nella forma stringa registrata dell'oggetto: questo è il linguaggio e possibilmente specifico del framework. In linea di principio si potrebbe sostituire il nome del tipo in qualsiasi sistema con una chiamata a un metodo della classe, nel qual caso ci sarebbe ignorare più che il costruttore in SystemDataHelperBuilder, avremmo anche ridefinire loggingname(). Quindi, se esiste una tale registrazione nel tuo sistema, potresti immaginare che sebbene la tua classe sovrascriva letteralmente solo il costruttore, "moralmente" sta anche sovrascrivendo il nome della classe, e quindi per scopi pratici non è una sottoclasse solo del costruttore. Ha un comportamento diverso desiderabile, è

Quindi, direi che il suo codice può essere una buona idea se c'è qualche codice altrove che in qualche modo controlla le istanze di SystemDataHelperBuilder, o esplicitamente con un ramo (come un'eccezione che cattura i rami in base al tipo) o forse perché c'è un codice generico che finirà usando il nome SystemDataHelperBuilderin modo utile (anche se si tratta solo di registrazione).

Tuttavia, l'uso di tipi per casi speciali porta a un po 'di confusione, dal momento che se ImmutableRectangle(1,1)e ImmutableSquare(1)comportarsi diversamente in qualche modo, alla fine qualcuno si chiederà perché e forse vorrebbe che non lo facesse. A differenza delle gerarchie di eccezioni, controlli se un rettangolo è un quadrato controllando se height == width, non con instanceof. Nel tuo esempio, c'è una differenza tra a SystemDataHelperBuildere a DataHelperBuildercreato con gli stessi argomenti esatti e che ha il potenziale per causare problemi. Pertanto, una funzione per restituire un oggetto creato con gli argomenti "giusti" mi sembra normalmente la cosa giusta da fare: la sottoclasse si traduce in due diverse rappresentazioni della cosa "stessa", e aiuta solo se stai facendo qualcosa che normalmente proverebbe a non farlo.

Si noti che sto non parlare in termini di modelli di progettazione e di anti-pattern, perché non credo che sia possibile fornire una tassonomia di tutte le buone e cattive idee che un programmatore ha mai pensato. Pertanto, non ogni idea è un modello o un anti-modello, ma diventa solo quando qualcuno identifica che si verifica ripetutamente in contesti diversi e lo nomina ;-) Non sono a conoscenza di alcun nome particolare per questo tipo di sottoclasse.


Ho potuto vedere gli usi per ImmutableSquareMatrix:ImmutableMatrix, a condizione che ci fosse un modo efficace per chiedere ImmutableMatrixdi renderne il contenuto come ImmutableSquareMatrixse possibile. Anche se ImmutableSquareMatrixfosse fondamentalmente un ImmutableMatrixcostruttore di cui era richiesta la corrispondenza di altezza e larghezza, e il cui AsSquareMatrixmetodo sarebbe semplicemente restituito (piuttosto che ad esempio costruire uno ImmutableSquareMatrixche avvolge lo stesso array di supporto), tale progetto consentirebbe la validazione dei parametri in fase di compilazione per i metodi che richiedono il quadrato matrici
supercat,

@supercat: buon punto, e nell'esempio dell'interrogatore se ci sono funzioni che devono essere passate a SystemDataHelperBuilder, e non tutte le altre DataHelperBuilderlo faranno, ha senso definire un tipo per quello (e un'interfaccia, supponendo che tu gestisca DI in quel modo) . Nel tuo esempio, ci sono alcune operazioni che una matrice quadrata potrebbe avere come un non quadrato, come determinante, ma il tuo punto è positivo che anche se il sistema non ha bisogno di quelle che vorresti comunque controllare per tipo .
Steve Jessop,

... quindi suppongo che non sia del tutto vero quello che ho detto, che "controlli se un rettangolo è un quadrato controllando se height == width, non con instanceof". Potresti preferire qualcosa che puoi affermare staticamente.
Steve Jessop,

Vorrei che esistesse un meccanismo che consentisse a qualcosa come la sintassi del costruttore di invocare metodi statici di fabbrica. Il tipo di espressione foo=new ImmutableMatrix(someReadableMatrix)è più chiaro di quello di someReadableMatrix.AsImmutable()o addirittura ImmutableMatrix.Create(someReadableMatrix), ma le ultime forme offrono utili possibilità semantiche che le prime mancano; se il costruttore può dire che la matrice è quadrata, essere in grado di far riflettere il suo tipo che potrebbe essere più pulito di richiedere il codice a valle che necessita di una matrice quadrata per creare una nuova istanza di oggetto.
supercat,

@supercat: Una piccola cosa di Python che mi piace è l'assenza di un newoperatore. Una classe è solo un richiamo, che quando chiamato capita di restituire (di default) un'istanza di se stessa. Quindi, se si desidera preservare la coerenza dell'interfaccia, è perfettamente possibile scrivere una funzione di fabbrica il cui nome inizia con una lettera maiuscola, quindi sembra proprio una chiamata del costruttore. Non che lo faccia abitualmente con le funzioni di fabbrica, ma è lì quando ne hai bisogno.
Steve Jessop,

2

La più rigorosa definizione orientata agli oggetti di una relazione di sottoclasse è nota come «is-a». Usando l'esempio tradizionale di un quadrato e un rettangolo, un rettangolo quadrato «is-a».

Con questo, dovresti usare la sottoclasse per ogni situazione in cui ritieni che un "is-a" sia una relazione significativa per il tuo codice.

Nel tuo caso specifico di una sottoclasse con nient'altro che un costruttore, dovresti analizzarlo da una prospettiva «is-a». È chiaro che un SystemDataHelperBuilder «è-a» DataHelperBuilder, quindi il primo passaggio suggerisce che è un uso valido.

La domanda a cui dovresti rispondere è se qualcuno degli altri codici sfrutta questa relazione «is-a». Qualche altro codice tenta di distinguere SystemDataHelperBuilders dalla popolazione generale di DataHelperBuilders? In tal caso, la relazione è abbastanza ragionevole e dovresti mantenere la sottoclasse.

Tuttavia, se nessun altro codice lo fa, allora quello che hai è una relazione che potrebbe essere una relazione «is-a» o che potrebbe essere implementata con una factory. In questo caso, potresti andare in entrambi i modi, ma consiglierei una relazione Factory piuttosto che una relazione "is-a". Quando si analizza il codice orientato agli oggetti scritto da un altro individuo, la gerarchia di classi è un'importante fonte di informazioni. Fornisce le relazioni «is-a» di cui avranno bisogno per dare un senso al codice. Di conseguenza, l'inserimento di questo comportamento in una sottoclasse promuove il comportamento da "qualcosa che qualcuno troverà se scava nei metodi della superclasse" a "qualcosa che tutti guarderanno prima ancora di iniziare a immergersi nel codice". Citando Guido van Rossum, "il codice viene letto più spesso di quanto non sia scritto" quindi ridurre la leggibilità del codice per i futuri sviluppatori è un prezzo rigido da pagare. Sceglierei di non pagarlo, se invece potessi usare una fabbrica.


1

Un sottotipo non è mai "sbagliato" purché sia ​​sempre possibile sostituire un'istanza del suo sottotipo con un'istanza del sottotipo e fare in modo che tutto funzioni ancora correttamente. Questo si verifica fino a quando la sottoclasse non cerca mai di indebolire nessuna delle garanzie offerte dal supertipo. Può offrire garanzie più forti (più specifiche), quindi in questo senso l'intuizione del collega è corretta.

Detto questo, la sottoclasse che hai creato probabilmente non è sbagliata (dal momento che non so esattamente cosa fanno, non posso dirlo con certezza). Devi stare attento al fragile problema della classe base e devi anche considera se interfaceti trarrebbero beneficio, ma è vero per tutti gli usi dell'eredità.


1

Penso che la chiave per rispondere a questa domanda sia guardare questo, particolare scenario di utilizzo.

L'ereditarietà viene utilizzata per applicare le opzioni di configurazione del database, come la stringa di connessione.

Questo è un modello anti perché la classe sta violando il Principio della singola responsabilità --- Si sta configurando con una fonte specifica di quella configurazione e non "Fare una cosa e farlo bene". La configurazione di una classe non dovrebbe essere inserita nella classe stessa. Per l'impostazione delle stringhe di connessione al database, la maggior parte delle volte l'ho visto accadere a livello di applicazione durante una sorta di evento che si verifica all'avvio dell'applicazione.


1

Uso le sottoclassi del costruttore solo abbastanza regolarmente per esprimere concetti diversi.

Ad esempio, considera la seguente classe:

public class Outputter
{
    private readonly IOutputMethod outputMethod;
    private readonly IDataMassager dataMassager;

    public Outputter(IOutputMethod outputMethod, IDataMassager dataMassager)
    {
        this.outputMethod = outputMethod;
        this.dataMassager = dataMassager;
    }

    public MassageDataAndOutput(string data)
    {
        this.outputMethod.Output(this.dataMassager.Massage(data));
    }
}

Possiamo quindi creare una sottoclasse:

public class CapitalizeAndPrintOutputter : Outputter
{
    public CapitalizeAndPrintOutputter()
        : base(new PrintOutputMethod(), new Capitalizer())
    {
    }
}

È quindi possibile utilizzare un CapitalizeAndPrintOutputter ovunque nel codice e si conosce esattamente che tipo di output è. Inoltre, questo modello in realtà rende molto facile usare un contenitore DI per creare questa classe. Se il contenitore DI è a conoscenza di PrintOutputMethod e Capitalizer, può persino creare automaticamente CapitalizeAndPrintOutputter per te.

L'uso di questo modello è davvero abbastanza flessibile e utile. Sì, puoi usare i metodi di fabbrica per fare la stessa cosa, ma poi (almeno in C #) perdi un po 'della potenza del sistema di tipi e certamente usare i contenitori DI diventa più ingombrante.


1

Consentitemi innanzitutto di notare che, man mano che il codice viene scritto, la sottoclasse non è in realtà più specifica del genitore, poiché i due campi inizializzati sono entrambi impostabili dal codice client.

Un'istanza della sottoclasse può essere distinta solo usando un downcast.

Ora, se si dispone di un progetto in cui le proprietà DatabaseEnginee ConnectionStringsono state reimplementate dalla sottoclasse, a cui si accede Configurationper farlo, allora si ha un vincolo che ha più senso avere la sottoclasse.

Detto questo, "anti-pattern" è troppo forte per i miei gusti; non c'è niente di veramente sbagliato qui.


0

Nell'esempio che hai dato, il tuo partner ha ragione. I due costruttori di helper di dati sono strategie. Uno è più specifico dell'altro, ma sono entrambi intercambiabili una volta inizializzati.

Fondamentalmente se un cliente del datahelperbuilder dovesse ricevere il systemdatahelperbuilder, nulla si spezzerebbe. Un'altra opzione sarebbe stata quella di fare in modo che systemdatahelperbuilder fosse un'istanza statica della classe datahelperbuilder.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.