Comment éviter une surcharge excessive de méthode?

16

Nous avons beaucoup d'endroits dans le code source de notre application, où une classe a de nombreuses méthodes avec les mêmes noms et différents paramètres. Ces méthodes ont toujours tous les paramètres d'une méthode «précédente» plus un de plus.

C'est le résultat d'une longue évolution (code hérité) et de cette réflexion (je crois):

" Il y a une méthode M qui fait la chose A. J'ai besoin de faire A + B. OK, je sais ... Je vais ajouter un nouveau paramètre à M, créer une nouvelle méthode pour cela, déplacer le code de M vers la nouvelle méthode avec un paramètre de plus, faites l'A + B là-bas et appelez la nouvelle méthode à partir de M avec une valeur par défaut du nouveau paramètre. "

Voici un exemple (en langage 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
    (...)
  }

  (...)
}

J'ai l'impression que c'est faux. Non seulement nous ne pouvons pas continuer à ajouter de nouveaux paramètres comme celui-ci pour toujours, mais le code est difficile à étendre / modifier en raison de toutes les dépendances entre les méthodes.

Voici quelques façons de mieux faire cela:

  1. Introduisez un objet paramètre:

    class DocumentCreationParams {
    
      String name;
      int minPagesCount;
      boolean firstPageBlank;
      String title;
    
      (...)
    }
    
    class DokumentHome {
    
      public Document createDocument(DocumentCreationParams p) {
        // here the real work gets done
        (...)
      }
    }
    
  2. Définissez les paramètres de l' DocumentHomeobjet avant d'appelercreateDocument()

      @In
      DocumentHome dh = null;
    
      (...)
    
      dh.setName(...);
      dh.setMinPagesCount(...);
      dh.setFirstPageBlank(...);
    
      Document newDocument = dh.createDocument();
    
  3. Séparez le travail en différentes méthodes et appelez-les selon vos besoins:

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

Mes questions:

  1. Le problème décrit est-il vraiment un problème?
  2. Que pensez-vous des solutions suggérées? Laquelle préféreriez-vous (en fonction de votre expérience)?
  3. Pouvez-vous penser à une autre solution?
Ytus
la source
1
Quelle langue visez-vous ou s'agit-il simplement de quelque chose?
Knerd
Aucune langue particulière, juste générale. N'hésitez pas à montrer comment les fonctionnalités d'autres langues peuvent vous y aider.
Ytus
comme je l'ai dit ici programmers.stackexchange.com/questions/235096/… C # et C ++ ont quelques fonctionnalités.
Knerd
Il est assez clair que cette question s'applique à tous les langages qui prennent en charge ce type de surcharge de méthode.
Doc Brown
1
@DocBrown ok, mais toutes les langues ne prennent pas en charge les mêmes alternatives;)
Knerd

Réponses:

20

Essayez peut-être le modèle de constructeur ? (note: résultat Google assez aléatoire :)

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

Je ne peux pas vous expliquer pourquoi je préfère le constructeur aux options que vous donnez, mais vous avez identifié un gros problème avec beaucoup de code. Si vous pensez que vous avez besoin de plus de deux paramètres pour une méthode, votre code est probablement mal structuré (et certains en diront un!).

Le problème avec un objet params est (sauf si l'objet que vous créez est en quelque sorte réel) vous poussez simplement le problème d'un niveau, et vous vous retrouvez avec un groupe de paramètres non liés formant `` l'objet ''.

Vos autres tentatives me ressemblent à quelqu'un qui cherche le modèle de constructeur mais qui n'y arrive pas tout à fait :)

Froome
la source
Je vous remercie. Votre réponse peut être la solution num. 4, oui. Je cherche plus de réponses de cette façon: «D'après mon expérience, cela conduit à ... et peut être corrigé ...». ou 'Quand je vois cela dans le code de mon collègue, je lui suggère de ... à la place.'
Ytus
Je ne sais pas ... il me semble que vous ne faites que déplacer le problème. Par exemple, si je peux construire un document sur différentes parties de l'application, il est préférable d'organiser et de tester cette isolation de cette construction de document sur une classe séparée (comme utiliser a DocumentoFactory). Avoir un buildersur différents endroits, il est difficile de contrôler les modifications futures de la construction du document (comme ajouter un nouveau champ obligatoire au document, par exemple) et ajouter du code passe-partout supplémentaire sur les tests pour simplement satisfaire les besoins du générateur de documents sur les classes qui utilisent le générateur.
Dherik
1

L'utilisation d'un objet paramètre est un bon moyen d'éviter une surcharge (excessive) des méthodes:

  • il nettoie le code
  • sépare les données de la fonctionnalité
  • rend le code plus maintenable

Je n'irais cependant pas trop loin.

Avoir une surcharge ici et là n'est pas une mauvaise chose. Il est pris en charge par le langage de programmation, alors utilisez-le à votre avantage.

Je n'étais pas au courant du modèle de constructeur, mais je l'ai utilisé "par accident" à quelques reprises. Il en va de même ici: n'en faites pas trop. Le code de votre exemple en bénéficierait, mais passer beaucoup de temps à l'implémenter pour chaque méthode qui a une seule méthode de surcharge n'est pas très efficace.

Juste mes 2 cents.

Rob
la source
0

Honnêtement, je ne vois pas de gros problème avec le code. En C # et C ++, vous pouvez utiliser des paramètres facultatifs, ce serait une alternative, mais pour autant que je sache, Java ne prend pas en charge ce type de paramètres.

En C #, vous pouvez rendre toutes les surcharges privées et une méthode avec des paramètres facultatifs est publique pour appeler le truc.

Pour répondre à votre question partie 2, je prendrais l'objet paramètre ou même un dictionnaire / HashMap.

Ainsi:

class DokumentHome {

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

Comme avertissement, je suis d'abord un programmeur C # et JavaScript, puis un programmeur Java.

Knerd
la source
4
C'est une solution, mais je ne pense pas que ce soit une bonne solution (du moins, pas dans un contexte où la sécurité du type est attendue).
Doc Brown
C'est vrai. À cause de tels cas, j'aime les méthodes surchargées ou les paramètres facultatifs.
Knerd du
2
L'utilisation d'un dictionnaire en tant que paramètre est un moyen simple de réduire le nombre de paramètres apparents à une méthode, mais elle masque les dépendances réelles de la méthode. Cela oblige l'appelant à chercher ailleurs ce qui doit être exactement dans le dictionnaire, que ce soit dans les commentaires, d'autres appels à la méthode ou l'implémentation de la méthode elle-même.
Mike Partridge
Utilisez le dictionnaire uniquement pour les paramètres vraiment facultatifs afin que les cas d'utilisation de base n'aient pas besoin de lire trop de documentation.
user949300
0

Je pense que c'est un bon candidat pour le modèle de constructeur. Le modèle de générateur est utile lorsque vous souhaitez créer des objets du même type, mais avec des représentations différentes.

Dans votre cas, j'aurais un constructeur avec les méthodes suivantes:

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

Vous pouvez ensuite utiliser le générateur de la manière suivante:

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

D'un autre côté, cela ne me dérange pas de quelques surcharges simples - mais que diable, le framework .NET les utilise partout avec des aides HTML. Cependant, je réévaluerais ce que je fais si je dois passer plus de deux paramètres à chaque méthode.

CodeART
la source
0

Je pense que la buildersolution peut fonctionner sur la plupart des scénarios, mais sur des cas plus complexes, votre constructeur sera également complexe à configurer , car il est facile de commettre des erreurs dans l'ordre des méthodes, quelles méthodes doivent être exposées ou non , etc. Ainsi, beaucoup d'entre nous préféreront une solution la plus simple.

Si vous venez de créer un simple générateur pour construire un document et diffuser ce code sur différentes parties (classes) de l'application, il sera difficile de:

  • organiser : vous aurez de nombreuses classes construisant un document de différentes manières
  • maintenir : tout changement sur l'instanciation du document (comme ajouter un nouveau fichier obligatoire) vous mènera à une chirurgie au fusil de chasse
  • test : si vous testez une classe qui construit un document, vous aurez besoin d'ajouter du code passe-partout juste pour satisfaire l'instanciation du document.

Mais cela ne répond pas à la question OP.

L'alternative à la surcharge

Quelques alternatives:

  • Renommez le nom de la méthode : si le même nom de méthode crée une certaine confusion, essayez de renommer les méthodes pour créer un nom significatif mieux que createDocument, comme:createLicenseDriveDocument , createDocumentWithOptionalFields, etc. Bien sûr, cela peut vous conduire à des noms de méthode géants, donc ce n'est pas une solution pour tous les cas.
  • Utilisez des méthodes statiques . Cette approche est un peu similaire par rapport à la première alternative ci-dessus. Vous pouvez utiliser un nom significatif pour chaque cas et instancier le document à partir d'une méthode statique surDocument , comme: Document.createLicenseDriveDocument().
  • Créez une interface commune : vous pouvez créer une seule méthode appelée createDocument(InterfaceDocument interfaceDocument)et créer différentes implémentations pour InterfaceDocument. Par exemple: createDocument(new DocumentMinPagesCount("name")). Bien sûr, vous n'avez pas besoin d'une seule implémentation pour chaque cas, car vous pouvez créer plus d'un constructeur sur chaque implémentation, en regroupant certains champs qui ont du sens sur cette implémentation. Ce modèle est appelé constructeurs télescopiques .

Ou restez simplement avec une solution de surcharge . Même étant, parfois, une solution laide, il n'y a pas beaucoup d'inconvénients à l'utiliser. Dans ce cas, je préfère utiliser des méthodes de surcharge sur une classe séparée, comme une DocumentoFactoryqui peut être injectée comme dépendance sur des classes qui ont besoin de créer des documents. Je peux organiser et valider les champs sans la complexité de créer un bon constructeur et de maintenir le code sur un seul endroit aussi.

Dherik
la source