Chaînage de méthodes - pourquoi est-ce une bonne pratique ou non?


151

Le chaînage de méthodes est la pratique des méthodes objet renvoyant l'objet lui-même afin que le résultat soit appelé pour une autre méthode. Comme ça:

participant.addSchedule(events[1]).addSchedule(events[2]).setStatus('attending').save()

Cela semble être considéré comme une bonne pratique, car cela produit du code lisible, ou une "interface fluide". Cependant, pour moi, cela semble au contraire casser la notation d'appel d'objet impliquée par l'orientation de l'objet elle-même - le code résultant ne représente pas l'exécution d'actions par rapport au résultat d'une méthode précédente, ce qui est généralement le mode de fonctionnement du code orienté objet:

participant.getSchedule('monday').saveTo('monnday.file')

Cette différence parvient à créer deux significations différentes pour la notation par points de "appeler l'objet résultant": Dans le contexte du chaînage, l'exemple ci-dessus se lirait comme une sauvegarde de l' objet participant , même si l'exemple est en fait destiné à enregistrer le planning objet reçu par getSchedule.

Je comprends que la différence ici est de savoir si la méthode appelée doit être attendue pour retourner quelque chose ou non (auquel cas elle renverrait l'objet appelé lui-même pour le chaînage). Mais ces deux cas ne se distinguent pas de la notation elle-même, seulement de la sémantique des méthodes appelées. Lorsque le chaînage de méthode n'est pas utilisé, je peux toujours savoir qu'un appel de méthode opère sur quelque chose lié au résultat de l'appel précédent - avec le chaînage, cette hypothèse se rompt, et je dois traiter sémantiquement toute la chaîne pour comprendre ce qu'est l'objet réel appelé est vraiment. Par exemple:

participant.attend(event).setNotifications('silent').getSocialStream('twitter').postStatus('Joining '+event.name).follow(event.getSocialId('twitter'))

Là, les deux derniers appels de méthode se réfèrent au résultat de getSocialStream, alors que les précédents font référence au participant. C'est peut-être une mauvaise pratique d'écrire des chaînes où le contexte change (n'est-ce pas?), Mais même dans ce cas, vous devrez constamment vérifier si les chaînes de points qui se ressemblent sont en fait conservées dans le même contexte, ou ne fonctionnent que sur le résultat .

Pour moi, il semble que si le chaînage de méthodes produit superficiellement du code lisible, surcharger le sens de la notation par points ne fait qu'engendrer plus de confusion. Comme je ne me considère pas comme un gourou de la programmation, je suppose que la faute est la mienne. Alors: qu'est-ce que je manque? Est-ce que je comprends mal le chaînage des méthodes? Y a-t-il des cas où le chaînage de méthodes est particulièrement bon, ou d'autres où il est particulièrement mauvais?

Note de bas de page: Je comprends que cette question puisse être lue comme une déclaration d'opinion masquée comme une question. Cependant, ce n'est pas - je veux vraiment comprendre pourquoi le chaînage est considéré comme une bonne pratique, et où est-ce que je me trompe en pensant qu'il rompt la notation orientée objet inhérente.


Il semble que le chaînage de méthodes est au moins un moyen d'écrire du code intelligent en java. Même si tout le monde n'est pas d'accord ..
Mercer Traieste

Ils appellent également ces méthodes "fluentes" ou une interface "fluide". Vous voudrez peut-être mettre à jour votre titre pour utiliser ce terme.
S.Lott le

4
Dans une autre discussion SO, il a été dit que l'interface fluide est un concept plus large qui a à voir avec la lisibilité du code, et le chaînage de méthodes n'est qu'une façon de viser cela. Ils sont étroitement liés, cependant, j'ai donc ajouté la balise et référencé des interfaces fluides dans le texte - je pense que cela suffit.
Ilari Kajaste

14
La façon dont j'en suis venu à penser à cela, c'est que le chaînage de méthodes est, en fait, un hackaround pour obtenir une fonctionnalité manquante dans la syntaxe du langage. Cela ne serait vraiment pas nécessaire s'il y avait une notation alternative intégrée .qui ignorerait les valeurs de retour de mehtod et invoquerait toujours les méthodes chaînées utilisant le même objet.
Ilari Kajaste

C'est un excellent idiome de codage, mais comme tous les bons outils, il est abusé.
Martin Spamer

Réponses:


74

Je reconnais que c'est subjectif. Pour la plupart, j'évite le chaînage de méthodes, mais récemment, j'ai également trouvé un cas où c'était juste la bonne chose - j'avais une méthode qui acceptait quelque chose comme 10 paramètres, et en avait besoin de plus, mais pour la plupart du temps, vous n'aviez qu'à spécifier un peu. Avec les dérogations, cela est devenu très fastidieux très rapidement. Au lieu de cela, j'ai opté pour l'approche de chaînage:

MyObject.Start()
    .SpecifySomeParameter(asdasd)
    .SpecifySomeOtherParameter(asdasd)
    .Execute();

L'approche de chaînage de méthodes était facultative, mais elle facilitait l'écriture de code (en particulier avec IntelliSense). Rappelez-vous qu'il s'agit d'un cas isolé et qu'il ne s'agit pas d'une pratique générale dans mon code.

Le fait est que dans 99% des cas, vous pouvez probablement faire aussi bien, voire mieux, sans chaînage de méthodes. Mais il y a le 1% où c'est la meilleure approche.


4
IMO, la meilleure façon d'utiliser le chaînage de méthodes dans ce scénario est de créer un objet paramètre à transmettre à la fonction, comme P = MyObject.GetParamsObj().SomeParameter(asdasd).SomeOtherParameter(asdasd); Obj = MyObject.Start(); MyObject.Execute(P);. Vous avez l'avantage de pouvoir réutiliser cet objet paramètre dans d'autres appels, ce qui est un plus!
pedromanoel

20
Juste mon centime de contribution sur les modèles, la méthode d'usine n'a généralement qu'un seul point de création et les produits sont un choix statique basé sur les paramètres de la méthode d'usine. La création de la chaîne ressemble plus à un modèle Builder où vous appelez différentes méthodes pour obtenir un résultat, les méthodes peuvent être facultatives comme dans le chaînage de méthodes , nous pouvons avoir quelque chose comme PizzaBuilder.AddSauce().AddDough().AddTopping()plus de références ici
Marco Medrano

3
Le chaînage de méthodes (comme indiqué dans la question d'origine) est considéré comme mauvais lorsqu'il enfreint la loi de déméter . Voir: ifacethoughts.net/2006/03/07/ ... La réponse donnée ici suit en fait cette loi car il s'agit d'un "modèle de constructeur".
Angel O'Sphere

2
@Marco Medrano L'exemple de PizzaBuilder m'a toujours dérangé depuis que je l'ai lu dans JavaWorld il y a très longtemps. Je pense que je devrais ajouter de la sauce à ma pizza, pas à mon chef.
Breandán Dalton

1
Je sais ce que tu veux dire, Vilx. Mais pourtant, quand j'ai lu list.add(someItem), j'ai lu cela comme "ce code s'ajoute someItemà l' listobjet". donc quand je lis PizzaBuilder.AddSauce(), je lis ceci naturellement comme "ce code ajoute de la sauce à l' PizzaBuilderobjet". En d'autres termes, je vois l'orchestrateur (celui qui fait l'ajout) comme la méthode dans laquelle le code list.add(someItem)ou PizzaBuilder.addSauce()est incorporé. Mais je pense juste que l'exemple de PizzaBuilder est un peu artificiel. Votre exemple de MyObject.SpecifySomeParameter(asdasd)fonctionne bien pour moi.
Breandán Dalton

78

Juste mes 2 cents;

Le chaînage de méthodes rend le débogage délicat: - Vous ne pouvez pas mettre le point d'arrêt dans un point concis afin de pouvoir mettre le programme en pause exactement où vous le souhaitez - Si l'une de ces méthodes lève une exception et que vous obtenez un numéro de ligne, vous n'avez aucune idée quelle méthode dans la «chaîne» a causé le problème.

Je pense que c'est généralement une bonne pratique de toujours écrire des lignes très courtes et concises. Chaque ligne ne doit faire qu'un seul appel de méthode. Préférez plus de lignes à des lignes plus longues.

EDIT: le commentaire mentionne que le chaînage de méthode et le saut de ligne sont séparés. C'est vrai. Cependant, selon le débogueur, il peut être possible ou non de placer un point d'arrêt au milieu d'une instruction. Même si vous le pouvez, l'utilisation de lignes séparées avec des variables intermédiaires vous donne beaucoup plus de flexibilité et tout un tas de valeurs que vous pouvez examiner dans la fenêtre de surveillance qui facilite le processus de débogage.


4
L'utilisation des nouvelles lignes et du chaînage de méthodes n'est-elle pas indépendante? Vous pouvez enchaîner avec chaque appel sur une nouvelle ligne selon @ Vilx-answer, et vous pouvez typiquement mettre plusieurs instructions séparées sur la même ligne (par exemple en utilisant des points-virgules en Java).
brabster

2
Cette réponse est pleinement justifiée, mais elle montre simplement une faiblesse présente dans tous les débogueurs que je connais et n'est pas spécifiquement liée à la question.
masterxilo

1
@Brabster. Ils peuvent être séparés pour certains débogueurs, cependant, avoir des appels séparés avec des variables intermédiaires vous donne toujours une plus grande richesse d'informations pendant que vous étudiez les bogues.
RAY

4
+1, à aucun moment vous ne savez ce que chaque méthode a renvoyé lors du débogage pas à pas d'une chaîne de méthodes. Le modèle de chaîne de méthodes est un hack. C'est le pire cauchemar d'un programmeur de maintenance.
Lee Kowalkowski

1
Je ne suis pas non plus un grand fan du chaînage, mais pourquoi ne pas simplement mettre un point d'arrêt dans les définitions de méthode?
Pankaj Sharma

39

Personnellement, je préfère les méthodes de chaînage qui n'agissent que sur l'objet d'origine, par exemple en définissant plusieurs propriétés ou en appelant des méthodes de type utilitaire.

foo.setHeight(100).setWidth(50).setColor('#ffffff');
foo.moveTo(100,100).highlight();

Je ne l'utilise pas lorsqu'une ou plusieurs des méthodes chaînées renverraient un objet autre que foo dans mon exemple. Bien que syntaxiquement, vous puissiez enchaîner n'importe quoi tant que vous utilisez l'API correcte pour cet objet dans la chaîne, le changement d'objets IMHO rend les choses moins lisibles et peut être vraiment déroutant si les API des différents objets présentent des similitudes. Si vous faites des appels de méthode vraiment commun à la fin ( .toString(), .print(), peu importe) quel objet vous agissez en fin de compte sur? Quelqu'un lisant le code avec désinvolture pourrait ne pas comprendre qu'il s'agirait d'un objet retourné implicitement dans la chaîne plutôt que de la référence d'origine.

Le chaînage de différents objets peut également entraîner des erreurs nulles inattendues. Dans mes exemples, en supposant que foo est valide, tous les appels de méthode sont "sûrs" (par exemple, valides pour foo). Dans l'exemple du PO:

participant.getSchedule('monday').saveTo('monnday.file')

... il n'y a aucune garantie (en tant que développeur externe regardant le code) que getSchedule retournera réellement un objet de planification valide et non nul. En outre, le débogage de ce style de code est souvent beaucoup plus difficile car de nombreux IDE n'évalueront pas l'appel de méthode au moment du débogage comme un objet que vous pouvez inspecter. OMI, chaque fois que vous pourriez avoir besoin d'un objet à inspecter à des fins de débogage, je préfère l'avoir dans une variable explicite.


S'il est possible que a Participantn'ait pas de méthode valide, Schedulealors la getScheduleméthode est conçue pour renvoyer un Maybe(of Schedule)type et la saveTométhode est conçue pour accepter un Maybetype.
Lightman

24

Martin Fowler a une bonne discussion ici:

Chaînage de méthodes

Quand l'utiliser

Le chaînage de méthodes peut ajouter beaucoup à la lisibilité d'un DSL interne et par conséquent est devenu presque un synonum pour les DSL internes dans certains esprits. Le chaînage de méthodes est préférable, cependant, lorsqu'il est utilisé en conjonction avec d'autres combinaisons de fonctions.

Le chaînage de méthodes est particulièrement efficace avec des grammaires comme parent :: = (this | that) *. L'utilisation de différentes méthodes fournit un moyen lisible de voir quel argument vient ensuite. De même, les arguments facultatifs peuvent être facilement ignorés avec le chaînage de méthodes. Une liste de clauses obligatoires, telles que parent :: = first second ne fonctionne pas aussi bien avec le formulaire de base, bien qu'elle puisse être bien supportée en utilisant des interfaces progressives. La plupart du temps, je préfère la fonction imbriquée pour ce cas.

Le plus gros problème pour le chaînage de méthodes est le problème de finition. Bien qu'il existe des solutions de contournement, généralement si vous rencontrez ce problème, il vaut mieux utiliser une fonction imbriquée. La fonction imbriquée est également un meilleur choix si vous vous retrouvez dans le pétrin avec les variables de contexte.


2
Le lien est mort :(
Qw3ry

Que voulez-vous dire par DSL? Langue spécifique du domaine
Sören

@ Sören: Fowler fait référence à des langages spécifiques à un domaine.
Dirk Vollmar

21

À mon avis, le chaînage de méthodes est un peu une nouveauté. Bien sûr, ça a l'air cool mais je n'y vois aucun avantage réel.

Comment est:

someList.addObject("str1").addObject("str2").addObject("str3")

pas mieux que:

someList.addObject("str1")
someList.addObject("str2")
someList.addObject("str3")

L'exception peut être lorsque addObject () renvoie un nouvel objet, auquel cas le code déchaîné peut être un peu plus encombrant comme:

someList = someList.addObject("str1")
someList = someList.addObject("str2")
someList = someList.addObject("str3")

9
C'est plus concis, car vous évitez deux des parties «someList» même dans votre premier exemple et vous vous retrouvez avec une ligne au lieu de trois. Maintenant, si c'est vraiment bon ou mauvais dépend de différentes choses et peut-être est-ce une question de goût.
Fabian Steeg

26
Le véritable avantage d'avoir 'someList' une seule fois, c'est qu'il est alors beaucoup plus facile de lui donner un nom plus long et plus descriptif. Chaque fois qu'un nom doit apparaître plusieurs fois de suite, il a tendance à le raccourcir (pour réduire la répétition et améliorer la lisibilité), ce qui le rend moins descriptif et nuit à la lisibilité.
Chris Dodd

Bon point sur la lisibilité et les principes DRY avec l'avertissement que le chaînage de méthodes empêche l'introspection de la valeur de retour d'une méthode donnée et / ou implique une présomption de listes / ensembles vides et de valeurs non nulles de chaque méthode de la chaîne (une erreur causant beaucoup de NPE dans les systèmes que je dois déboguer / réparer souvent).
Darrell Teague du

@ChrisDodd Vous n'avez jamais entendu parler de la complétion automatique?
inf3rno

1
"le cerveau humain est très bon pour reconnaître la répétition de texte s'il a la même indentation" - c'est précisément le problème de la répétition: cela amène le cerveau à se concentrer sur le motif répétitif. Donc, pour lire ET COMPRENDRE le code, vous devez forcer votre cerveau à regarder au-delà / derrière le motif répétitif pour voir ce qui se passe réellement, c'est-à-dire dans les différences entre le motif répétitif que votre cerveau a tendance à ignorer. C'est pourquoi la répétition est si mauvaise pour la lisibilité du code.
Chris Dodd

7

C'est dangereux car vous pouvez dépendre de plus d'objets que prévu, comme alors votre appel renvoie une instance d'une autre classe:

Je vais donner un exemple:

foodStore est un objet composé de nombreux magasins d'alimentation que vous possédez. foodstore.getLocalStore () retourne un objet qui contient des informations sur le magasin le plus proche du paramètre. getPriceforProduct (n'importe quoi) est une méthode de cet objet.

Ainsi, lorsque vous appelez foodStore.getLocalStore (paramètres) .getPriceforProduct (n'importe quoi)

vous dépendez non seulement de FoodStore comme vous l'avez pensé, mais aussi de LocalStore.

Si getPriceforProduct (quelque chose) change un jour, vous devez modifier non seulement FoodStore mais aussi la classe qui a appelé la méthode chaînée.

Vous devez toujours viser un couplage lâche entre les classes.

Cela étant dit, j'aime personnellement les enchaîner lors de la programmation de Ruby.


7

Beaucoup utilisent l'enchaînement de méthodes comme une forme de commodité plutôt que d'avoir à l'esprit des problèmes de lisibilité. Le chaînage de méthodes est acceptable s'il implique d'effectuer la même action sur le même objet - mais seulement s'il améliore réellement la lisibilité, et pas seulement pour écrire moins de code.

Malheureusement, beaucoup utilisent le chaînage de méthodes selon les exemples donnés dans la question. Bien qu'ils puissent toujours être rendus lisibles, ils provoquent malheureusement un couplage élevé entre plusieurs classes, ce n'est donc pas souhaitable.


6

Cela semble assez subjectif.

L'enchaînement de méthodes n'est pas quelque chose qui est intrinsèquement mauvais ou bon imo.

La lisibilité est la chose la plus importante.

(Considérez également que le fait d'avoir un grand nombre de méthodes enchaînées rendra les choses très fragiles si quelque chose change)


Il peut en effet être subjectif, d'où l'étiquette subjective. Ce que j'espère que les réponses me mettront en évidence, c'est dans quels cas l'enchaînement de méthodes serait une bonne idée - pour le moment, je n'en vois pas grand-chose, mais je pense que c'est juste mon incapacité à comprendre les bons points du concept, plutôt que quelque chose de intrinsèquement mauvais dans le chaînage lui-même.
Ilari Kajaste

Ne serait-il pas fondamentalement mauvais si cela aboutissait à un couplage élevé? Briser la chaîne en déclarations individuelles ne réduit pas la lisibilité.
aberrant80

1
dépend de la façon dont vous voulez être dogmatique. Si cela se traduit par quelque chose de plus lisible, cela pourrait être préférable dans de nombreuses situations. Le gros problème que j'ai avec cette approche est que la plupart des méthodes sur un objet renverront des références à l'objet lui-même mais souvent la méthode renverra une référence à un objet enfant sur lequel vous pouvez enchaîner plus de méthodes. Une fois que vous commencez à faire cela, il devient très difficile pour un autre codeur de démêler ce qui se passe. De plus, tout changement de fonctionnalité d'une méthode sera difficile à déboguer dans une grande instruction composée.
John Nicholas

6

Avantages du chaînage,
c'est-à-dire là où j'aime l'utiliser

Un avantage du chaînage que je n'ai pas vu mentionné était la possibilité de l'utiliser lors de l'initiation des variables, ou lors du passage d'un nouvel objet à une méthode, je ne sais pas si c'est une mauvaise pratique ou non.

Je sais que c'est un exemple artificiel mais disons que vous avez les cours suivants

Public Class Location
   Private _x As Integer = 15
   Private _y As Integer = 421513

   Public Function X() As Integer
      Return _x
   End Function
   Public Function X(ByVal value As Integer) As Location
      _x = value
      Return Me
   End Function

   Public Function Y() As Integer
      Return _y
   End Function
   Public Function Y(ByVal value As Integer) As Location
      _y = value
      Return Me
   End Function

   Public Overrides Function toString() As String
      Return String.Format("{0},{1}", _x, _y)
   End Function
End Class

Public Class HomeLocation
   Inherits Location

   Public Overrides Function toString() As String
      Return String.Format("Home Is at: {0},{1}", X(), Y())
   End Function
End Class

Et disons que vous n'avez pas accès à la classe de base, ou dites que les valeurs par défaut sont dynamiques, basées sur le temps, etc. Oui, vous pouvez instancier alors, puis changer les valeurs mais cela peut devenir fastidieux, surtout si vous ne faites que passer les valeurs d'une méthode:

  Dim loc As New HomeLocation()
  loc.X(1337)
  PrintLocation(loc)

Mais n'est-ce pas simplement beaucoup plus facile à lire:

  PrintLocation(New HomeLocation().X(1337))

Ou, qu'en est-il d'un membre de la classe?

Public Class Dummy
   Private _locA As New Location()
   Public Sub New()
      _locA.X(1337)
   End Sub
End Class

contre

Public Class Dummy
   Private _locC As Location = New Location().X(1337)
End Class

C'est ainsi que j'utilise le chaînage, et généralement mes méthodes sont juste pour la configuration, elles ne font donc que 2 lignes, définissez une valeur, puis Return Me. Pour nous, il a nettoyé d'énormes lignes très difficiles à lire et à comprendre le code en une seule ligne qui se lit comme une phrase. quelque chose comme

New Dealer.CarPicker().Subaru.WRX.SixSpeed.TurboCharged.BlueExterior.GrayInterior.Leather.HeatedSeats

Vs Quelque chose comme

New Dealer.CarPicker(Dealer.CarPicker.Makes.Subaru
                   , Dealer.CarPicker.Models.WRX
                   , Dealer.CarPicker.Transmissions.SixSpeed
                   , Dealer.CarPicker.Engine.Options.TurboCharged
                   , Dealer.CarPicker.Exterior.Color.Blue
                   , Dealer.CarPicker.Interior.Color.Gray
                   , Dealer.CarPicker.Interior.Options.Leather
                   , Dealer.CarPicker.Interior.Seats.Heated)

Au détriment du chaînage,
c'est-à-dire là où je n'aime pas l'utiliser

Je n'utilise pas le chaînage quand il y a beaucoup de paramètres à passer aux routines, principalement parce que les lignes deviennent très longues, et comme l'OP l'a mentionné, cela peut devenir déroutant lorsque vous appelez des routines à d'autres classes pour passer à l'une des les méthodes de chaînage.

Il y a aussi le souci qu'une routine renvoie des données non valides, donc jusqu'à présent, je n'ai utilisé le chaînage que lorsque je retourne la même instance appelée. Comme cela a été souligné, si vous enchaînez entre les classes, vous rendez le débogage plus difficile (lequel a renvoyé null?) Et pouvez augmenter le couplage de dépendances entre les classes.

Conclusion

Comme tout dans la vie et la programmation, le chaînage n'est ni bon, ni mauvais si vous pouvez éviter le mauvais alors le chaînage peut être un grand avantage.

J'essaye de suivre ces règles.

  1. Essayez de ne pas enchaîner entre les classes
  2. Créer des routines spécifiquement pour le chaînage
  3. Ne faites qu'UNE seule chose dans une routine de chaînage
  4. Utilisez-le quand il améliore la lisibilité
  5. Utilisez-le quand cela simplifie le code

6

Le chaînage de méthodes peut permettre de concevoir directement des DSL avancés en Java. En substance, vous pouvez modéliser au moins ces types de règles DSL:

1. SINGLE-WORD
2. PARAMETERISED-WORD parameter
3. WORD1 [ OPTIONAL-WORD]
4. WORD2 { WORD-CHOICE-A | WORD-CHOICE-B }
5. WORD3 [ , WORD3 ... ]

Ces règles peuvent être implémentées à l'aide de ces interfaces

// Initial interface, entry point of the DSL
interface Start {
  End singleWord();
  End parameterisedWord(String parameter);
  Intermediate1 word1();
  Intermediate2 word2();
  Intermediate3 word3();
}

// Terminating interface, might also contain methods like execute();
interface End {}

// Intermediate DSL "step" extending the interface that is returned
// by optionalWord(), to make that method "optional"
interface Intermediate1 extends End {
  End optionalWord();
}

// Intermediate DSL "step" providing several choices (similar to Start)
interface Intermediate2 {
  End wordChoiceA();
  End wordChoiceB();
}

// Intermediate interface returning itself on word3(), in order to allow for
// repetitions. Repetitions can be ended any time because this interface
// extends End
interface Intermediate3 extends End {
  Intermediate3 word3();
}

Avec ces règles simples, vous pouvez implémenter des DSL complexes tels que SQL directement en Java, comme le fait jOOQ , une bibliothèque que j'ai créée. Voir un exemple SQL assez complexe tiré de mon blog ici:

create().select(
    r1.ROUTINE_NAME,
    r1.SPECIFIC_NAME,
    decode()
        .when(exists(create()
            .selectOne()
            .from(PARAMETERS)
            .where(PARAMETERS.SPECIFIC_SCHEMA.equal(r1.SPECIFIC_SCHEMA))
            .and(PARAMETERS.SPECIFIC_NAME.equal(r1.SPECIFIC_NAME))
            .and(upper(PARAMETERS.PARAMETER_MODE).notEqual("IN"))),
                val("void"))
        .otherwise(r1.DATA_TYPE).as("data_type"),
    r1.NUMERIC_PRECISION,
    r1.NUMERIC_SCALE,
    r1.TYPE_UDT_NAME,
    decode().when(
    exists(
        create().selectOne()
            .from(r2)
            .where(r2.ROUTINE_SCHEMA.equal(getSchemaName()))
            .and(r2.ROUTINE_NAME.equal(r1.ROUTINE_NAME))
            .and(r2.SPECIFIC_NAME.notEqual(r1.SPECIFIC_NAME))),
        create().select(count())
            .from(r2)
            .where(r2.ROUTINE_SCHEMA.equal(getSchemaName()))
            .and(r2.ROUTINE_NAME.equal(r1.ROUTINE_NAME))
            .and(r2.SPECIFIC_NAME.lessOrEqual(r1.SPECIFIC_NAME)).asField())
    .as("overload"))
.from(r1)
.where(r1.ROUTINE_SCHEMA.equal(getSchemaName()))
.orderBy(r1.ROUTINE_NAME.asc())
.fetch()

Un autre bel exemple est jRTF , un petit DSL conçu pour la certification de documents RTF directement en Java. Un exemple:

rtf()
  .header(
    color( 0xff, 0, 0 ).at( 0 ),
    color( 0, 0xff, 0 ).at( 1 ),
    color( 0, 0, 0xff ).at( 2 ),
    font( "Calibri" ).at( 0 ) )
  .section(
        p( font( 1, "Second paragraph" ) ),
        p( color( 1, "green" ) )
  )
).out( out );

@ user877329: Oui, il peut être utilisé dans à peu près n'importe quel langage de programmation orienté objet qui connaît quelque chose comme les interfaces et le polymorphisme des sous-types
Lukas Eder

4

Le chaînage de méthodes peut être simplement une nouveauté dans la plupart des cas, mais je pense qu'il a sa place. Un exemple peut être trouvé dans l'utilisation de l'enregistrement actif de CodeIgniter :

$this->db->select('something')->from('table')->where('id', $id);

Cela semble beaucoup plus propre (et a plus de sens, à mon avis) que:

$this->db->select('something');
$this->db->from('table');
$this->db->where('id', $id);

C'est vraiment subjectif; Tout le monde a sa propre opinion.


Ceci est un exemple d'interface Fluent avec chaînage de méthodes, donc UseCase est légèrement différent ici. Vous n'êtes pas seulement en train de chaîner, mais vous créez un langage interne spécifique au domaine qui se lit facilement. En passant, l'ActiveRecord de CI n'est pas un ActiveRecord.
Gordon

3

Je pense que l'erreur principale est de penser qu'il s'agit d'une approche orientée objet en général alors qu'en fait, il s'agit plus d'une approche de programmation fonctionnelle qu'autre chose.

Les principales raisons pour lesquelles je l'utilise sont à la fois pour la lisibilité et pour éviter que mon code ne soit inondé de variables.

Je ne comprends pas vraiment de quoi parlent les autres quand ils disent que cela nuit à la lisibilité. C'est l'une des formes de programmation les plus concises et cohérentes que j'ai utilisées.

Aussi ceci:

convertTextToVoice.LoadText ("source.txt"). ConvertToVoice ("destination.wav");

est la façon dont je l'utilise généralement. L'utiliser pour enchaîner x nombre de paramètres n'est pas la façon dont je l'utilise généralement. Si je voulais mettre un nombre x de paramètres dans un appel de méthode, j'utiliserais la syntaxe params :

public void foo (objet params [] éléments)

Et lancez les objets en fonction du type ou utilisez simplement un tableau ou une collection de types de données en fonction de votre cas d'utilisation.


1
+1 sur "l'erreur principale est de penser qu'il s'agit d'une approche orientée objet en général alors qu'en fait il s'agit plus d'une approche de programmation fonctionnelle qu'autre chose". Le cas d'utilisation le plus important concerne les manipulations sans état sur des objets (où au lieu de changer son état, vous retournez un nouvel objet sur lequel vous continuez à agir). Les questions et autres réponses du PO montrent des actions avec état qui semblent en effet gênantes avec l'enchaînement.
OmerB

Oui, vous avez raison, c'est une manipulation sans état, sauf que je ne crée généralement pas de nouvel objet, mais j'utilise plutôt l'injection de dépendances pour en faire un service disponible. Et oui, les cas d'utilisation avec état ne sont pas ce à quoi je pense que le chaînage de méthodes était destiné. La seule exception que je vois est si vous initialisez le service DI avec certains paramètres et avez une sorte de chien de garde pour surveiller l'état comme un service COM d'une sorte. Juste à mon humble avis.
Shane Thorndike le

2

Je suis d'accord, j'ai donc changé la façon dont une interface fluide était implémentée dans ma bibliothèque.

Avant:

collection.orderBy("column").limit(10);

Après:

collection = collection.orderBy("column").limit(10);

Dans l'implémentation "avant", les fonctions ont modifié l'objet et se sont terminées par return this. J'ai changé l'implémentation pour renvoyer un nouvel objet du même type .

Mon raisonnement pour ce changement :

  1. La valeur de retour n'avait rien à voir avec la fonction, elle était purement là pour supporter la partie chaînage, cela aurait dû être une fonction vide selon la POO.

  2. Le chaînage de méthodes dans les bibliothèques système l'implémente également de cette façon (comme linq ou string):

    myText = myText.trim().toUpperCase();
    
  3. L'objet d'origine reste intact, ce qui permet à l'utilisateur de l'API de décider quoi en faire. Il permet:

    page1 = collection.limit(10);
    page2 = collection.offset(10).limit(10);
    
  4. Une implémentation de copie peut également être utilisée pour créer des objets:

    painting = canvas.withBackground('white').withPenSize(10);
    

    Où la setBackground(color)fonction change l'instance et ne renvoie rien (comme c'est supposé le faire) .

  5. Le comportement des fonctions est plus prévisible (voir points 1 et 2).

  6. L'utilisation d'un nom de variable court peut également réduire l'encombrement du code, sans forcer une API sur le modèle.

    var p = participant; // create a reference
    p.addSchedule(events[1]);p.addSchedule(events[2]);p.setStatus('attending');p.save()
    

Conclusion:
À mon avis, une interface fluide qui utilise une return thisimplémentation est tout simplement fausse.


1
Mais le retour d'une nouvelle instance pour chaque appel ne créerait-il pas un peu de surcharge, surtout si vous utilisez des éléments plus volumineux? Au moins pour les langues non gérées.
Apeiron

@Apeiron En termes de performances, il est nettement plus rapide à return thisgérer ou non. C'est mon avis qu'il obtient une api fluide d'une manière "non naturelle". (Raison supplémentaire 6: pour montrer une alternative non fluide, qui n'a pas la surcharge / fonctionnalité ajoutée)
Bob Fanger

Je suis d'accord avec toi. Il est généralement préférable de laisser l'état sous-jacent d'un DSL intact et de renvoyer de nouveaux objets à chaque appel de méthode, à la place ... Je suis curieux: quelle est cette bibliothèque que vous avez mentionnée?
Lukas Eder

1

Le point totalement manqué ici, c'est que l'enchaînement de méthodes permet le DRY . C'est un substitut efficace pour le "avec" (qui est mal implémenté dans certaines langues).

A.method1().method2().method3(); // one A

A.method1();
A.method2();
A.method3(); // repeating A 3 times

Cela compte pour la même raison que DRY compte toujours; si A s'avère être une erreur et que ces opérations doivent être effectuées sur B, vous n'avez besoin de mettre à jour qu'à 1 endroit, pas 3.

Pragmatiquement, l'avantage est faible dans ce cas. Pourtant, un peu moins de frappe, un peu plus robuste (DRY), je vais le prendre.


14
La répétition d'un nom de variable dans le code source n'a rien à voir avec le principe DRY. DRY déclare que "Chaque élément de connaissance doit avoir une représentation unique, sans ambiguïté et faisant autorité au sein d'un système", ou en d'autres termes, éviter la répétition de la connaissance (pas la répétition de texte ).
pedromanoel

4
C'est très certainement la répétition qui viole à sec. La répétition des noms de variables (inutilement) cause tout le mal de la même manière que les autres formes de dry do: cela crée plus de dépendances et plus de travail. Dans l'exemple ci-dessus, si nous renommons A, la version humide aura besoin de 3 changements et provoquera des erreurs de débogage si l'un des trois est omis.
Anfurny

1
Je ne vois pas le problème que vous avez signalé dans cet exemple, car tous les appels de méthode sont proches les uns des autres. De plus, si vous oubliez de modifier le nom d'une variable sur une ligne, le compilateur renverra une erreur et cela sera corrigé avant que vous puissiez exécuter votre programme. De plus, le nom d'une variable est limité à la portée de sa déclaration. Sauf si cette variable est globale, ce qui est déjà une mauvaise pratique de programmation. IMO, DRY ne consiste pas à moins taper, mais à garder les choses isolées.
pedromanoel

Le "compilateur" peut renvoyer une erreur, ou peut-être que vous utilisez PHP ou JS et l'interpréteur ne peut émettre une erreur au moment de l'exécution que lorsque vous atteignez cette condition.
Anfurny

1

Je déteste généralement le chaînage de méthodes car je pense que cela aggrave la lisibilité. La compacité est souvent confondue avec la lisibilité, mais ce ne sont pas les mêmes termes. Si vous faites tout dans une seule instruction, alors c'est compact, mais c'est moins lisible (plus difficile à suivre) la plupart du temps que de le faire dans plusieurs instructions. Comme vous l'avez remarqué, à moins que vous ne puissiez garantir que la valeur de retour des méthodes utilisées est la même, le chaînage des méthodes sera une source de confusion.

1.)

participant
    .addSchedule(events[1])
    .addSchedule(events[2])
    .setStatus('attending')
    .save();

contre

participant.addSchedule(events[1]);
participant.addSchedule(events[2]);
participant.setStatus('attending');
participant.save()

2.)

participant
    .getSchedule('monday')
        .saveTo('monnday.file');

contre

mondaySchedule = participant.getSchedule('monday');
mondaySchedule.saveTo('monday.file');

3.)

participant
    .attend(event)
    .setNotifications('silent')
    .getSocialStream('twitter')
        .postStatus('Joining '+event.name)
        .follow(event.getSocialId('twitter'));

contre

participant.attend(event);
participant.setNotifications('silent')
twitter = participant.getSocialStream('twitter')
twitter.postStatus('Joining '+event.name)
twitter.follow(event.getSocialId('twitter'));

Comme vous pouvez le voir, vous ne gagnez presque rien, car vous devez ajouter des sauts de ligne à votre instruction unique pour la rendre plus lisible et vous devez ajouter une indentation pour indiquer clairement que vous parlez d'objets différents. Eh bien, si je voulais utiliser un langage basé sur l'identification, j'apprendrais Python au lieu de le faire, sans oublier que la plupart des IDE supprimeront l'indentation en formatant automatiquement le code.

Je pense que le seul endroit où ce type de chaînage peut être utile est de canaliser des flux dans CLI ou de joindre plusieurs requêtes ensemble dans SQL. Les deux ont un prix pour plusieurs déclarations. Mais si vous voulez résoudre des problèmes complexes, vous vous retrouverez même avec ceux qui en paient le prix et qui écrivent le code dans plusieurs instructions en utilisant des variables ou en écrivant des scripts bash et des procédures ou vues stockées.

Comme des interprétations DRY: "Évitez la répétition de la connaissance (pas la répétition de texte)." et "Tapez moins, ne répétez même pas les textes.", le premier ce que signifie vraiment le principe, mais le second est un malentendu commun car beaucoup de gens ne peuvent pas comprendre des conneries trop compliquées comme "Chaque connaissance doit avoir une seule, sans ambiguïté, représentation faisant autorité dans un système ". Le second est la compacité à tout prix, qui rompt dans ce scénario, car elle aggrave la lisibilité. La première interprétation rompt par DDD lorsque vous copiez du code entre des contextes limités, car le couplage lâche est plus important dans ce scénario.


0

Le bon:

  1. C'est concis, mais vous permet d'en mettre plus dans une seule ligne avec élégance.
  2. Vous pouvez parfois éviter d'utiliser une variable, ce qui peut parfois être utile.
  3. Cela peut mieux fonctionner.

Le mauvais:

  1. Vous implémentez des retours, en ajoutant essentiellement des fonctionnalités aux méthodes sur des objets qui ne font pas vraiment partie de ce que ces méthodes sont censées faire. Il renvoie quelque chose que vous avez déjà uniquement pour économiser quelques octets.
  2. Il masque les changements de contexte lorsqu'une chaîne en entraîne une autre. Vous pouvez l'obtenir avec des getters, sauf que c'est assez clair lorsque le contexte change.
  3. Le chaînage sur plusieurs lignes semble laid, ne joue pas bien avec l'indentation et peut causer une certaine confusion dans la gestion des opérateurs (en particulier dans les langues avec ASI).
  4. Si vous voulez commencer à renvoyer quelque chose d'autre qui est utile pour une méthode chaînée, vous allez potentiellement avoir plus de mal à le réparer ou à rencontrer plus de problèmes.
  5. Vous transférez le contrôle à une entité vers laquelle vous ne déchargeriez normalement pas pour des raisons pratiques, même dans des langages strictement typés, les erreurs causées par cela ne peuvent pas toujours être détectées.
  6. Cela peut être pire.

Général:

Une bonne approche consiste à ne pas utiliser le chaînage en général jusqu'à ce que des situations surviennent ou que des modules spécifiques y soient particulièrement adaptés.

Le chaînage peut nuire gravement à la lisibilité dans certains cas, en particulier lors de la pesée aux points 1 et 2.

Lors de l'accasation, il peut être mal utilisé, comme au lieu d'une autre approche (passer un tableau par exemple) ou mélanger des méthodes de manière bizarre (parent.setSomething (). GetChild (). SetSomething (). GetParent (). SetSomething ()).


0

Réponse avisée

Le plus gros inconvénient du chaînage est qu'il peut être difficile pour le lecteur de comprendre comment chaque méthode affecte l'objet d'origine, si c'est le cas, et quel type chaque méthode retourne.

Quelques questions:

  • Les méthodes de la chaîne renvoient-elles un nouvel objet ou le même objet muté?
  • Toutes les méthodes de la chaîne renvoient-elles le même type?
  • Sinon, comment est indiqué lorsqu'un type dans la chaîne change?
  • La valeur renvoyée par la dernière méthode peut-elle être supprimée en toute sécurité?

Le débogage, dans la plupart des langages, peut en effet être plus difficile avec le chaînage. Même si chaque étape de la chaîne est sur sa propre ligne (ce qui va à l'encontre de l'objectif du chaînage), il peut être difficile d'inspecter la valeur renvoyée après chaque étape, en particulier pour les méthodes non mutantes.

Les temps de compilation peuvent être plus lents en fonction du langage et du compilateur, car les expressions peuvent être beaucoup plus complexes à résoudre.

Je crois que comme pour tout, le chaînage est une bonne solution qui peut être pratique dans certains scénarios. Il doit être utilisé avec prudence, en comprenant les implications et en limitant le nombre d'éléments de la chaîne à quelques-uns.


0

Dans les langages typés (qui manquent autoou équivalent), cela évite à l'implémenteur d'avoir à déclarer les types des résultats intermédiaires.

import Participant
import Schedule

Participant participant = new Participant()
... snip...
Schedule s = participant.getSchedule(blah)
s.saveTo(filename)

Pour des chaînes plus longues, vous pourriez avoir affaire à plusieurs types intermédiaires différents, vous devrez déclarer chacun d'eux.

Je crois que cette approche s'est vraiment développée en Java où a) tous les appels de fonction sont des invocations de fonctions membres, et b) des types explicites sont requis. Bien sûr, il y a un compromis ici en termes, perdant une certaine explication, mais dans certaines situations, certaines personnes trouvent que cela vaut la peine.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.