Come cambia il concetto di una classe quando si passano i dati al costruttore invece dei parametri del metodo?


12

Diciamo che stiamo facendo un parser. Un'implementazione potrebbe essere:

public sealed class Parser1
{
    public string Parse(string text)
    {
       ...
    }
}

Oppure potremmo invece passare il testo al costruttore:

public sealed class Parser2
{
    public Parser2(string text)
    {
       this.text = text;
    }

    public string Parse()
    {
       ...
    }
}

L'utilizzo è semplice in entrambi i casi, ma cosa significa abilitare l'input di parametri Parser1rispetto all'altro? Quale messaggio ho inviato a un altro programmatore quando guardano l'API? Inoltre, ci sono vantaggi / svantaggi tecnici in alcuni casi?

Un'altra domanda sorge quando mi rendo conto che un'interfaccia sarebbe piuttosto insignificante nella seconda implementazione:

public interface IParser
{
    string Parse();
}

... in cui un'interfaccia sulla prima potrebbe servire almeno a qualche scopo. Significa qualcosa in particolare, che una classe è "interfacciabile" o no?


Questa è la prima volta che vedo alcune utili domande e risposte su come la semantica di OO esprima realmente l'intento. Fino ad ora è stato tutto zucchero sintattico, per me.

Risposte:


11

Semanticamente parlando, in OOP dovresti solo passare al costruttore un insieme di parametri necessari per costruire la classe - allo stesso modo quando chiami un metodo, dovresti solo passargli i parametri necessari per eseguire la sua logica di business.

I parametri che è necessario passare nel costruttore sono parametri che non hanno alcun valore predefinito ragionevole e se la classe è immutabile (o effettivamente a struct), è necessario passare le proprietà non predefinite.

Per quanto riguarda i tuoi due esempi:

  • se passi textal costruttore, suggerisce che la Parser2classe sarà costruita appositamente per analizzare quell'istanza di testo in un secondo momento. Sarà un parser specifico. Questo è in genere il caso in cui la costruzione della classe è molto costosa o sottile, un RegEx potrebbe essere compilato nel costruttore, quindi una volta che hai un'istanza puoi riutilizzarla senza dover pagare il costo della compilazione; un altro esempio è l'inizializzazione del PRNG: è meglio se lo si fa raramente.
  • se passi textal metodo, segnala che Parser1può essere riutilizzato per analizzare diversi testi tramite chiamate.

3
Aggiungerei che c'è un debole segnale che Parser1 mantiene lo stato, ovvero che una particolare stringa di testo può produrre risultati diversi a seconda che sia stata precedentemente eseguita su tat. Questo non è necessariamente così, ma potrebbe essere.
jmoreno,

8

Bene, ricordiamo cosa significa passare una variabile come parametro del costruttore: inizializzi un oggetto per usare le sue variabili di istanza nei metodi dell'oggetto. Il punto è che probabilmente vuoi usarlo in più di un metodo poiché vuoi avere un'alta coesione nella tua classe.

Passare un parametro direttamente a un metodo significa in un certo senso inviare un messaggio a un oggetto e probabilmente ricevere una risposta. Con ciò, il cliente vuole che l'oggetto fornisca un servizio per lui.

Quindi, in conclusione, questi sono due modi molto diversi di passare i parametri e dovresti scegliere se il tuo oggetto deve fornire un servizio o fornire alcune funzionalità intrinsecamente mentre gestisci alcune informazioni internamente.


+1. La coesione è il modo in cui decido se appartiene al metodo o al costruttore.
jhewlett,

4

L'utilizzo è semplice in entrambi i casi, ma cosa significa abilitare l'input di parametri in Parser1, rispetto all'altro?

È un cambiamento di progettazione fondamentale. E il design dovrebbe trasmettere intento e significato. Devi avere oggetti separati per ogni stringa che vuoi analizzare? In altre parole, perché abbiamo bisogno di un'istanza di parser con stringX e un'altra istanza con stringY? Cosa c'è nell'analisi (ing) e nella stringa data che i due devono vivere e morire insieme? Supponendo che l '"implementazione [analisi] sottostante" (come dice Robert Harvey) non cambi, non sembra avere senso. E anche allora il suo discutibile IMHO.

Come cambia il concetto di una classe quando si passano i dati al costruttore invece dei parametri del metodo?

I parametri del costruttore mi dicono che queste cose sono necessarie per un oggetto. Lo stato corretto non è garantito senza di loro. Inoltre, so come / perché un parser è fondamentalmente diverso dall'altro.

I parametri del costruttore mi impediscono di sapere troppo su come usare la classe. Se invece dovrei impostare determinate proprietà, come faccio a saperlo? Si apre un'intera lattina di vermi. Quali proprietà? In quale ordine? Prima di usare quali metodi? e così via.

Un'altra domanda sorge quando mi rendo conto che un'interfaccia sarebbe piuttosto insignificante nella seconda implementazione:

Un'interfaccia, come nell'API, è i metodi e le proprietà esposti al codice client. Non lasciarti avvolgere public interface { ... }esclusivamente. Quindi il significato dell'interfaccia è nel dilemma dei parametri metodo o costruttore o metodo, NON public interface Iparservspublic sealed class Parser

La sealedclasse è strana. Se sto pensando a diverse implementazioni di parser - hai menzionato "Iparser" - allora l'ereditarietà è il mio primo pensiero. È solo una naturale estensione concettuale nel mio pensiero. IE tutti gli ParserXs sono fondamentalmente Parsers. In che altro modo dirlo? ... Un pastore tedesco è un cane (eredità), ma posso addestrare il mio pappagallo ad abbaiare (comportarsi come un cane - "interfaccia"); ma Polly non è un cane, finge semplicemente di aver imparato un sottoinsieme di dogness. Le classi, astratte o meno, servono perfettamente come interfacce .


Se cammina come un parser e parla come un parser, è ... un'anatra!

2

La seconda versione della classe può essere resa immutabile.

L'interfaccia può ancora essere utilizzata per fornire la possibilità di scambiare l'implementazione sottostante.


Non può essere reso immutabile anche nella prima versione, passando i dati all'interno della classe in modo funzionale?
ciscoheat,

2
Assolutamente. Ma il modello generale di immutabilità è quello di impostare i membri della classe usando il costruttore e avere proprietà di sola lettura. Per la programmazione funzionale, non hai nemmeno bisogno di una classe.
Robert Harvey,

Scilla e cariddi: scelgo OO o dati immutabili? Non l'ho mai sentito dire così.

2

Parser1

Costruire con un costruttore predefinito e passare il testo di input in un metodo implica che Parser1 è riutilizzabile.

Parser2

Passare il testo di input nel costruttore implica che è necessario creare un nuovo Parser2 per ogni stringa di input.


Certo, quindi se lo portiamo al livello successivo, cosa concludere su un oggetto riutilizzabile rispetto a un non riutilizzabile?
ciscoheat,

Non assumerei nulla di più, invece di rimandare alla documentazione.
Mike Partridge,

Un "oggetto riutilizzabile" in cui la sua identità cambia è un non sequitur. E con i framework gestiti che significano che non devi nemmeno buttare via le cose, semplicemente sovrascriverle o farle uscire dal campo di applicazione, non è certamente necessario. Costruisci via!
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.