Come evitare un sovraccarico eccessivo del metodo?


16

Abbiamo un sacco di posti nel codice sorgente della nostra applicazione, in cui una classe ha molti metodi con gli stessi nomi e parametri diversi. Tali metodi hanno sempre tutti i parametri di un metodo "precedente" più uno in più.

È il risultato di una lunga evoluzione (codice legacy) e di questo pensiero (credo):

" Esiste un metodo M che fa la cosa A. Ho bisogno di fare A + B. OK, lo so ... Aggiungerò un nuovo parametro a M, creerò un nuovo metodo per quello, sposterò il codice da M al nuovo metodo con un altro parametro, esegui A + B laggiù e chiama il nuovo metodo da M con un valore predefinito del nuovo parametro. "

Ecco un esempio (in linguaggio simile a Java):

class DocumentHome {

  (...)

  public Document createDocument(String name) {
    // just calls another method with default value of its parameter
    return createDocument(name, -1);
  }

  public Document createDocument(String name, int minPagesCount) {
    // just calls another method with default value of its parameter
    return createDocument(name, minPagesCount, false);
  }

  public Document createDocument(String name, int minPagesCount, boolean firstPageBlank) {
    // just calls another method with default value of its parameter
    return createDocument(name, minPagesCount, false, "");
  }

  public Document createDocument(String name, int minPagesCount, boolean firstPageBlank, String title) {
    // here the real work gets done
    (...)
  }

  (...)
}

Sento che questo è sbagliato. Non solo non possiamo continuare ad aggiungere nuovi parametri come questo per sempre, ma il codice è difficile da estendere / modificare a causa di tutte le dipendenze tra i metodi.

Ecco alcuni modi per farlo meglio:

  1. Introdurre un oggetto parametro:

    class DocumentCreationParams {
    
      String name;
      int minPagesCount;
      boolean firstPageBlank;
      String title;
    
      (...)
    }
    
    class DokumentHome {
    
      public Document createDocument(DocumentCreationParams p) {
        // here the real work gets done
        (...)
      }
    }
    
  2. Impostare i parametri DocumentHomesull'oggetto prima di chiamarecreateDocument()

      @In
      DocumentHome dh = null;
    
      (...)
    
      dh.setName(...);
      dh.setMinPagesCount(...);
      dh.setFirstPageBlank(...);
    
      Document newDocument = dh.createDocument();
    
  3. Separare il lavoro in diversi metodi e chiamarli secondo necessità:

      @In
      DocumentHome dh = null;
    
      Document newDocument = dh.createDocument();
      dh.changeName(newDocument, "name");
      dh.addFirstBlankPage(newDocument);
      dh.changeMinPagesCount(new Document, 10);
    

Le mie domande:

  1. Il problema descritto è davvero un problema?
  2. Cosa ne pensi delle soluzioni suggerite? Quale preferiresti (in base alla tua esperienza)?
  3. Riesci a pensare a qualche altra soluzione?

1
Quale lingua stai prendendo di mira o è solo sth generell?
Knerd,

Nessuna lingua particolare, solo generale. Sentiti libero di mostrare come funzioni in altre lingue possono aiutarti in questo.
Ytus,

come ho detto qui programmers.stackexchange.com/questions/235096/… C # e C ++ hanno alcune funzionalità.
Knerd

È abbastanza chiaro che questa domanda si applica a tutte le lingue che supportano questo tipo di sovraccarico di metodi.
Doc Brown,

1
@DocBrown ok, ma non tutte le lingue supportano le stesse alternative;)
Knerd

Risposte:


20

Forse provare il modello del costruttore ? (nota: risultato Google abbastanza casuale :)

var document = new DocumentBuilder()
                   .FirstPageBlank()
                   .Name("doc1final(2).doc")
                   .MinimumNumberOfPages(4)
                   .Build();

Non posso dare una panoramica completa del motivo per cui preferisco il builder alle opzioni che offri, ma hai identificato un grosso problema con molto codice. Se pensi di aver bisogno di più di due parametri per un metodo, probabilmente il tuo codice è strutturato in modo errato (e alcuni ne discuterebbero uno!).

Il problema con un oggetto params è (a meno che l'oggetto che crei non sia in qualche modo reale) spingi il problema a un livello superiore e finisci con un gruppo di parametri non correlati che formano l '"oggetto".

I tuoi altri tentativi mi sembrano come qualcuno che sta cercando il modello del costruttore ma non ci sta arrivando :)


Grazie. La tua risposta può essere num num. 4 si. Sto cercando più risposte in questo modo: "Nella mia esperienza questo porta a ... e può essere risolto ...". o "Quando lo vedo nel codice del mio collega, gli suggerisco di ... invece."
Ytus,

Non lo so ... mi sembra che tu stia spostando il problema. Ad esempio, se riesco a costruire un documento su diverse parti dell'applicazione, è meglio organizzare e testare questo isolando questa costruzione del documento su una classe separata (come usare a DocumentoFactory). Avere una builderposizione diversa è difficile controllare le modifiche future sulla costruzione del documento (come aggiungere un nuovo campo obbligatorio per documentare, ad esempio) e aggiungere un codice aggiuntivo sulla piastra di prova nei test per soddisfare solo le necessità del generatore di documenti sulle classi che utilizzano il generatore.
Dherik

1

L'uso di un oggetto parametro è un buon modo per evitare il sovraccarico (eccessivo) di metodi:

  • pulisce il codice
  • separa i dati dalla funzionalità
  • rende il codice più gestibile

Tuttavia, non ci andrei troppo lontano.

Avere un sovraccarico qui e non c'è una cosa negativa. È supportato dal linguaggio di programmazione, quindi utilizzalo a tuo vantaggio.

Non ero a conoscenza del modello del costruttore, ma l'ho usato "per caso" in alcune occasioni. Lo stesso vale qui: non esagerare. Il codice nel tuo esempio ne trarrebbe beneficio, ma impiegare molto tempo per implementarlo per ogni metodo che ha un singolo metodo di overload non è molto efficiente.

Solo i miei 2 centesimi.


0

Onestamente non vedo un grosso problema con il codice. In C # e C ++ puoi usare parametri opzionali, che sarebbe un'alternativa, ma per quanto ne so Java non supporta quel tipo di parametri.

In C # potresti rendere privati ​​tutti i sovraccarichi e un metodo con parametri opzionali è pubblico per chiamare roba.

Per rispondere alla tua domanda parte 2, prenderei l'oggetto parametro o anche un dizionario / HashMap.

Così:

class DokumentHome {

  public Document createDocument(Map<string, object> params) {
    if (params.containsKey("yourkey")) {
       // do that
    }
    // here the real work gets done
    (...)
  }
}

Come disclaimer, sono prima un programmatore C # e JavaScript e poi un programmatore Java.


4
È una soluzione, ma non penso che sia una buona soluzione (almeno, non in un contesto in cui è prevista la sicurezza dei caratteri).
Doc Brown,

Giusto. A causa di casi del genere, mi piacciono i metodi sovraccarichi o i parametri opzionali.
Knerd,

2
L'uso di un dizionario come parametro è un modo semplice per ridurre il numero di parametri apparenti a un metodo, ma oscura le dipendenze effettive del metodo. Ciò costringe il chiamante a cercare altrove ciò che deve essere esattamente nel dizionario, sia esso nei commenti, altre chiamate al metodo o l'implementazione del metodo stesso.
Mike Partridge,

Usa il dizionario solo per parametri veramente opzionali , quindi i casi d'uso di base non devono leggere troppa documentazione.
user949300,

0

Penso che questo sia un buon candidato per il modello del costruttore. Il modello Builder è utile quando si desidera creare oggetti dello stesso tipo, ma con rappresentazioni diverse.

Nel tuo caso, avrei un costruttore con i seguenti metodi:

SetTitle (Document document) { ... }
SetFirstPageBlank (Document document) { ... }
SetMinimumPageCount (Document document) { ... }

È quindi possibile utilizzare il builder nel modo seguente:

_builder.SetTitle(document);
_builder.SetFirstPageBlank(document);

A parte questo, non mi occupo di alcuni semplici sovraccarichi: che diavolo, .NET framework li usa ovunque con gli helper HTML. Tuttavia, rivaluterei ciò che sto facendo se devo passare più di due parametri a ciascun metodo.


0

Penso che la buildersoluzione possa funzionare nella maggior parte degli scenari, ma in casi più complessi il builder sarà anche complesso da configurare , perché è facile commettere alcuni errori nell'ordine dei metodi, quali metodi devono essere esposti o meno , ecc. Quindi, molti di noi preferiranno una soluzione più semplice.

Se si crea un semplice builder per creare un documento e diffondere questo codice su parti (classi) diverse dell'applicazione, sarà difficile:

  • organizza : avrai molte classi che costruiscono un documento in modi diversi
  • mantenere : qualsiasi modifica all'istanza del documento (come aggiungere un nuovo deposito obbligatorio) condurrà ad un intervento chirurgico con fucile a pompa
  • test : se si sta testando una classe che costruisce un documento, sarà necessario aggiungere il codice boilerplate solo per soddisfare l'istanza del documento.

Ma questo non risponde alla domanda del PO.

L'alternativa al sovraccarico

Alcune alternative:

  • Rinominare il nome del metodo : se lo stesso nome del metodo è la creazione di una certa confusione, provare a rinominare i metodi per creare un nome significativo di meglio che createDocument, come: createLicenseDriveDocument, createDocumentWithOptionalFields, ecc Naturalmente, questo può portare a voi i nomi dei metodi giganti, quindi questo non è una soluzione per tutti i casi.
  • Usa metodi statici . Questo approccio è in qualche modo simile rispetto alla prima alternativa sopra. È possibile utilizzare un nome significativo per ogni singolo caso e istanziare il documento da un metodo statico su Document, come: Document.createLicenseDriveDocument().
  • Crea un'interfaccia comune : puoi creare un singolo metodo chiamato createDocument(InterfaceDocument interfaceDocument)e creare diverse implementazioni per InterfaceDocument. Per esempio: createDocument(new DocumentMinPagesCount("name")). Ovviamente non è necessaria un'unica implementazione per ogni caso, poiché è possibile creare più di un costruttore su ciascuna implementazione, raggruppando alcuni campi che hanno senso su tale implementazione. Questo modello è chiamato costruttori telescopici .

Oppure resta con la soluzione di sovraccarico . Pur essendo, a volte, una brutta soluzione, non ci sono molti svantaggi nell'usarlo. In tal caso, preferisco usare metodi di sovraccarico su una classe separata, come una DocumentoFactoryche può essere iniettata come dipendenza da classi che devono creare documenti. Sono in grado di organizzare e validare i campi senza la complessità di creare un buon costruttore e mantenere il codice in un unico posto.

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.