I metodi di sovraccarico che fanno cose logicamente diverse, rompono i principi principali?

5

Questo è qualcosa che mi ha infastidito per un po 'ora. In alcuni casi vedi codice che è una serie di sovraccarichi, ma quando guardi all'attuazione reale ti rendi conto di fare cose logicamente diverse. Tuttavia, la loro scrittura come overload consente al chiamante di ignorarlo e ottenere lo stesso risultato finale. Ma sarebbe più corretto nominare i metodi in modo più esplicito e poi scriverli come overload?

public void LoadWords(string filePath)
{
    var lines = File.ReadAllLines(filePath).ToList();

    LoadWords(lines);
}

public void LoadWords(IEnumerable<string> words)
{
    // loads words into a List<string> based on some filters
}

Questi metodi potrebbero servire meglio gli sviluppatori futuri a essere nominati come LoadWordsFromFile() e LoadWordsFromEnumerable() ? Mi sembra inutile, ma se fosse meglio quale principio di programmazione si applicherebbe qui?

Il rovescio della medaglia renderebbe così non hai bisogno di leggere le firme per vedere esattamente come è possibile caricare le parole, che come dice lo zio Bob sarebbe un doppio ciak. Ma in generale questo tipo di sovraccarico deve essere evitato allora?

    
posta siva.k 24.08.2014 - 21:54
fonte

3 risposte

10

Mi chiedo davvero perché la versione con IO effettivo sia stata aggiunta alla classe in primo luogo. Qual è il punto qui? Come è questa la responsabilità della classe?

Perché non aggiungere un metodo per caricare le parole da un file zippato, o da un file .xml o .json o .doc o da un database SQL o su http o ftp o altro? Perché non è responsabilità della classe, ecco perché!

In particolare, il metodo basato sul nome del file aggiunge una dipendenza rispetto a un oggetto globale. Che è tutto tranne che ideale se me lo chiedi.

Quindi direi che il principio di programmazione che si applicherebbe qui è la composizione.

  • loader.LoadWords(someList)
  • loader.LoadWords(File.ReadAllLines(somePath).ToList())
  • loader.LoadWords(Http.get(someUrl).split(NEWLINE))
  • ...

Unire un particolare metodo di caricamento dei dati e un particolare metodo di elaborazione non dovrebbe essere fatto nella classe stessa, ma in un altro modulo, la cui responsabilità è di eseguire solo quella composizione. Potrebbe essere un contenitore IoC o qualcosa del genere, ma non deve essere così elegante.

    
risposta data 24.08.2014 - 22:53
fonte
5

Questo viola almeno tre principi di programmazione orientata agli oggetti e potenzialmente un quarto.

In primo luogo, questo viola il Principio di Almostonment . Supponiamo di avere una documentazione per uno solo dei metodi, e dobbiamo dedurre ciò che fa l'altro. Primo:

// Reads words from a file
public void LoadWords(string)

// ???
public void LoadWords(IEnumerable<string>)

Sarebbe molto facile guardare il primo e pensare che il secondo dovrebbe leggere le parole da un elenco di file. Quello sarebbe un comportamento coerente. Tuttavia, non lo è. Ora il contrario:

// ???
public void LoadWords(string)

// Loads the IEnumerable of strings into words
public void LoadWords(IEnumerable<string>)

Ora sarebbe molto facile guardare il secondo, e pensare che il primo dovrebbe caricare una singola parola. Invece, avrei cercato errori IO o potenzialmente qualcosa di totalmente inaspettato (se la parola capita di abbinare un nome di file reale) se provassi ad usarlo come tale. Questo metodo è difficile da ragionare perché i comportamenti sono così diversi.

In secondo luogo, questo codice viola la Legge di Demeter . In un metodo, ora devo raggiungere e interagire con il file system, che è un oggetto totalmente non correlato a un livello completamente diverso di astrazione. Dove prima avevo a che fare con il filtraggio e il salvataggio delle parole, ora devo preoccuparmi del codice del file system. Devo sapere come arrivare al file system, aprire un file, leggere un file, chiudere un file, assegnare un token al contenuto del file, rilevare eventuali eccezioni a cui potrebbero incorrere, ecc. Questa non dovrebbe essere la responsabilità di questa classe, che indica che si tratta di una violazione del principio di responsabilità singola .

Infine, possiamo anche violare il principio Open-Closed . Supponiamo di dover sovraccaricare il metodo per leggere da un flusso di rete. Dobbiamo aprire questa classe per aggiungere la funzionalità aggiuntiva. Ciò aggrava anche le violazioni Demeter e SRP.

Se i metodi vengono denominati in modo più esplicito, iniziamo a filtrare i dettagli di implementazione della nostra classe. In questo caso, la ragione è che le responsabilità non dovrebbero trovarsi nella nostra classe. Dovremmo spingere quelle decisioni di implementazione ai clienti della classe. Ciò consente loro di prendere la decisione, come già stavano facendo, ma offre loro molta più flessibilità e controllo, alleviando al contempo la tua classe dal peso di gestire tutti quei dettagli internamente.

La risposta di back2dos mostra come ottenere ciò tramite il codice client.

    
risposta data 25.08.2014 - 17:44
fonte
0

Elaborando la risposta @ back2dos, ci sono due cose nel codice.

  1. caricamento di parole in un elenco basato su alcuni filtri
  2. analizzando le parole (in questo caso, da un file, dividendo su nuove righe)

Quindi voterei per due metodi. Nota - Sono un ragazzo di Java quindi sto indovinando sui tipi ...

  void filterWords(IEnumerable<string> rawwords) // note - why return void instead of List?
  List<string> parseWords(InputStream in)

Il fatto che appartengano a classi separate o a una classe dipende dal modo specifico di suddividere le righe su questa particolare classe. Se ciò dovesse accadere in altri punti del tuo codice (o puoi facilmente immaginarlo accadere in futuro) che dovrebbe essere scisso.

Usando uno stream al posto di un nome file in parseWords si guadagna un po 'di flessibilità e astrazione (ad esempio, è possibile utilizzare un Http GET). Forse stream non è la giusta astrazione, se è così, pensa a ciò che è giusto.

    
risposta data 25.08.2014 - 18:45
fonte

Leggi altre domande sui tag