O encadeamento de métodos é a prática de métodos de objetos retornando o próprio objeto para que o resultado seja chamado para outro método. Como isso:
participant.addSchedule(events[1]).addSchedule(events[2]).setStatus('attending').save()
Isso parece ser considerado uma boa prática, pois produz código legível ou uma "interface fluente". No entanto, para mim, ao contrário, parece quebrar a notação de chamada de objeto implícita na própria orientação a objeto - o código resultante não representa a execução de ações ao resultado de um método anterior, que é como geralmente se espera que o código orientado a objetos funcione:
participant.getSchedule('monday').saveTo('monnday.file')
Essa diferença consegue criar dois significados diferentes para a notação de ponto de "chamar o objeto resultante": No contexto do encadeamento, o exemplo acima leria como salvando o objeto participante , mesmo que o exemplo pretenda salvar o agendamento. objeto recebido por getSchedule.
Eu entendo que a diferença aqui é se o método chamado deve retornar algo ou não (nesse caso, ele retornaria o próprio objeto chamado para encadeamento). Mas esses dois casos não são distinguíveis da própria notação, apenas da semântica dos métodos chamados. Quando o encadeamento de métodos não é usado, sempre posso saber que uma chamada de método opera com algo relacionado ao resultado da chamada anterior - com encadeamento, essa suposição é interrompida e eu tenho que processar semanticamente toda a cadeia para entender qual é o objeto real chamado realmente é. Por exemplo:
participant.attend(event).setNotifications('silent').getSocialStream('twitter').postStatus('Joining '+event.name).follow(event.getSocialId('twitter'))
Lá, as duas últimas chamadas de método se referem ao resultado de getSocialStream, enquanto as anteriores se referem ao participante. Talvez seja uma prática ruim escrever cadeias onde o contexto muda (não é?), Mas mesmo assim você terá que verificar constantemente se cadeias de pontos que parecem semelhantes são de fato mantidas no mesmo contexto ou apenas funcionam no resultado .
Para mim, parece que, embora o encadeamento de métodos produza superficialmente código legível, sobrecarregar o significado da notação de ponto resulta apenas em mais confusão. Como não me considero um guru da programação, presumo que a culpa seja minha. Então: o que estou perdendo? Entendo o encadeamento de métodos de alguma forma errado? Existem alguns casos em que o encadeamento de métodos é especialmente bom ou alguns em que é especialmente ruim?
Sidenote: Entendo que essa pergunta poderia ser lida como uma declaração de opinião mascarada como uma pergunta. Porém, não é - eu realmente quero entender por que o encadeamento é considerado uma boa prática, e onde errei ao pensar que isso quebra a notação inerente à orientação a objetos.
fonte
.
interna que ignorasse os valores de retorno do método e sempre invocasse métodos encadeados usando o mesmo objeto.Respostas:
Eu concordo que isso é subjetivo. Na maioria das vezes, evito o encadeamento de métodos, mas recentemente também encontrei um caso em que era a coisa certa - eu tinha um método que aceitava algo como 10 parâmetros e precisava de mais, mas na maioria das vezes você só precisava especificar um poucos. Com as substituições, isso se tornou muito complicado, muito rápido. Em vez disso, optei pela abordagem de encadeamento:
A abordagem de encadeamento de métodos era opcional, mas facilitou a escrita de código (especialmente com o IntelliSense). Lembre-se de que este é um caso isolado e não é uma prática geral no meu código.
O ponto é - em 99% dos casos, você provavelmente pode se sair tão bem ou até melhor sem o encadeamento de métodos. Mas existe o 1% em que essa é a melhor abordagem.
fonte
P = MyObject.GetParamsObj().SomeParameter(asdasd).SomeOtherParameter(asdasd); Obj = MyObject.Start(); MyObject.Execute(P);
. Você tem a vantagem de poder reutilizar esse objeto de parâmetro em outras chamadas, o que é uma vantagem!PizzaBuilder.AddSauce().AddDough().AddTopping()
mais referências aquilist.add(someItem)
, li como "esse código está sendo adicionadosomeItem
aolist
objeto". então, quando leioPizzaBuilder.AddSauce()
, leio isso naturalmente como "esse código está adicionando molho aoPizzaBuilder
objeto". Em outras palavras, vejo o orquestrador (aquele que está adicionando) como o método no qual o códigolist.add(someItem)
ouPizzaBuilder.addSauce()
está incorporado. Mas acho que o exemplo do PizzaBuilder é um pouco artificial. Seu exemplo deMyObject.SpecifySomeParameter(asdasd)
funciona bem para mim.Apenas meus 2 centavos;
O encadeamento de métodos torna a depuração complicada: - Você não pode colocar o ponto de interrupção em um ponto conciso para pausar o programa exatamente onde deseja - Se um desses métodos gerar uma exceção e você obtiver um número de linha, você não tem idéia qual método na "cadeia" causou o problema.
Geralmente, é uma boa prática escrever sempre linhas muito curtas e concisas. Cada linha deve apenas fazer uma chamada de método. Prefira mais linhas a linhas mais longas.
EDIT: o comentário menciona que o encadeamento do método e a quebra de linha são separados. Isso é verdade. Porém, dependendo do depurador, pode ou não ser possível colocar um ponto de interrupção no meio de uma instrução. Mesmo que você possa, o uso de linhas separadas com variáveis intermediárias oferece muito mais flexibilidade e vários valores que você pode examinar na janela Observar, que ajuda no processo de depuração.
fonte
Pessoalmente, prefiro métodos de encadeamento que atuam apenas no objeto original, por exemplo, definindo várias propriedades ou chamando métodos do tipo utilitário.
Eu não o uso quando um ou mais métodos encadeados retornam qualquer objeto que não seja foo no meu exemplo. Embora sintaticamente você possa encadear qualquer coisa, desde que esteja usando a API correta para esse objeto na cadeia, a alteração de objetos IMHO torna as coisas menos legíveis e pode ser realmente confusa se as APIs dos diferentes objetos tiverem semelhanças. Se você fizer alguma chamada de método muito comum no final (
.toString()
,.print()
, qualquer que seja) que objeto está em última análise, agindo sobre? Alguém lendo o código casualmente pode não entender que seria um objeto retornado implicitamente na cadeia, e não a referência original.Encadear objetos diferentes também pode levar a erros nulos inesperados. Nos meus exemplos, assumindo que foo é válido, todas as chamadas de método são "seguras" (por exemplo, válidas para foo). No exemplo do OP:
... não há garantia (como desenvolvedor externo olhando o código) de que getSchedule retornará realmente um objeto de agendamento válido e não nulo. Além disso, a depuração desse estilo de código geralmente é muito mais difícil, pois muitos IDEs não avaliam a chamada do método no momento da depuração como um objeto que você pode inspecionar. IMO, sempre que você precisar de um objeto para inspecionar para fins de depuração, prefiro tê-lo em uma variável explícita.
fonte
Participant
não ter um válidoSchedule
, ogetSchedule
método é projetado para retornar umMaybe(of Schedule)
tipo e osaveTo
método é projetado para aceitar umMaybe
tipo.Martin Fowler tem uma boa discussão aqui:
fonte
Na minha opinião, o encadeamento de métodos é uma novidade. Claro, parece legal, mas não vejo vantagens reais nele.
Como é:
melhor que:
A exceção pode ser quando addObject () retorna um novo objeto. Nesse caso, o código desencadeado pode ser um pouco mais complicado, como:
fonte
É perigoso porque você pode estar dependendo de mais objetos do que o esperado, assim como sua chamada retorna uma instância de outra classe:
Eu vou dar um exemplo:
O foodStore é um objeto composto por muitas lojas de alimentos que você possui. foodstore.getLocalStore () retorna um objeto que mantém as informações na loja mais próxima do parâmetro. getPriceforProduct (qualquer coisa) é um método desse objeto.
Portanto, quando você chama foodStore.getLocalStore (parameters) .getPriceforProduct (qualquer coisa)
você depende não apenas do FoodStore como também, mas também do LocalStore.
Se getPriceforProduct (qualquer coisa) mudar, você precisará alterar não apenas o FoodStore, mas também a classe que chamou o método encadeado.
Você deve sempre procurar um acoplamento flexível entre as classes.
Dito isto, eu pessoalmente gosto de encadeá-los ao programar Ruby.
fonte
Muitos usam o encadeamento de métodos como uma forma de conveniência, em vez de se preocuparem com a legibilidade. O encadeamento de métodos é aceitável se envolver a execução da mesma ação no mesmo objeto - mas apenas se ele realmente melhorar a legibilidade e não apenas para escrever menos código.
Infelizmente, muitos usam o encadeamento de métodos conforme os exemplos dados na pergunta. Embora eles ainda possam ser tornados legíveis, infelizmente estão causando alto acoplamento entre várias classes, portanto, não é desejável.
fonte
Isso parece meio subjetivo.
O encadeamento de métodos não é algo inerentemente ruim ou bom.
Legibilidade é a coisa mais importante.
(Considere também que ter um grande número de métodos encadeados tornará as coisas muito frágeis se algo mudar)
fonte
Benefícios do encadeamento
, ou seja, onde eu gosto de usá-lo
Um benefício do encadeamento que não vi mencionado foi a capacidade de usá-lo durante a iniciação de variável ou ao passar um novo objeto para um método, sem ter certeza se isso é uma prática ruim ou não.
Eu sei que este é um exemplo artificial, mas digamos que você tenha as seguintes classes
E diga que você não tem acesso à classe base, ou Diga que os valores padrão são dinâmicos, baseados no tempo, etc. Sim, você pode instanciar e alterar os valores, mas isso pode se tornar complicado, especialmente se você está passando os valores para um método:
Mas isso não é muito mais fácil de ler:
Ou o que acontece com um aluno?
vs
É assim que tenho usado o encadeamento e, normalmente, meus métodos são apenas para configuração, portanto, eles têm apenas 2 linhas, definem um valor
Return Me
. Para nós, limpou linhas enormes muito difíceis de ler e entender código em uma linha que parecia uma frase. algo comoVs algo como
Detriment Of Chaining
ou seja, onde eu não gosto de usá-lo
Eu não uso encadeamento quando há muitos parâmetros para passar para as rotinas, principalmente porque as linhas ficam muito longas e, como o OP mencionou, pode ficar confuso quando você está chamando rotinas para outras classes para passar para uma das os métodos de encadeamento.
Há também a preocupação de que uma rotina retorne dados inválidos, até agora eu só usei encadeamento quando estou retornando a mesma instância que está sendo chamada. Como foi apontado, se você encadeia entre classes, torna a depuração mais difícil (qual retornou nulo?) E pode aumentar o acoplamento de dependências entre as classes.
Conclusão
Como tudo na vida e programação, o encadeamento não é bom nem ruim, se você pode evitar o mal, o encadeamento pode ser um grande benefício.
Eu tento seguir estas regras.
fonte
O encadeamento de métodos pode permitir projetar DSLs avançadas em Java diretamente. Em essência, você pode modelar pelo menos esses tipos de regras DSL:
Essas regras podem ser implementadas usando essas interfaces
Com essas regras simples, você pode implementar DSLs complexas, como SQL diretamente em Java, como é feito pelo jOOQ , uma biblioteca que eu criei. Veja um exemplo SQL bastante complexo, retirado do meu blog aqui:
Outro bom exemplo é o jRTF , um pouco de DSL projetado para analisar documentos RTF diretamente em Java. Um exemplo:
fonte
O encadeamento de métodos pode ser simplesmente uma novidade para a maioria dos casos, mas acho que tem o seu lugar. Um exemplo pode ser encontrado no uso do Active Record do CodeIgniter :
Isso parece muito mais limpo (e faz mais sentido, na minha opinião) do que:
É realmente subjetivo; Todos tem sua própria opinião.
fonte
Eu acho que a principal falácia é pensar que esta é uma abordagem orientada a objetos em geral, quando na verdade é mais uma abordagem de programação funcional do que qualquer outra coisa.
As principais razões pelas quais eu o uso são tanto para facilitar a leitura quanto para impedir que meu código seja inundado por variáveis.
Eu realmente não entendo do que os outros estão falando quando dizem que isso prejudica a legibilidade. É uma das formas de programação mais concisas e coesas que já usei.
Também isto:
convertTextToVoice.LoadText ("source.txt"). ConvertToVoice ("destination.wav");
é assim que eu normalmente usaria. Usá-lo para encadear x número de parâmetros não é como eu normalmente o uso. Se eu quisesse colocar um número x de parâmetros em uma chamada de método, usaria a sintaxe params :
public void foo (objeto de params [] itens)
E projete os objetos com base no tipo ou apenas use uma matriz ou coleção de tipos de dados, dependendo do seu caso de uso.
fonte
Concordo que mudei a maneira como uma interface fluente foi implementada na minha biblioteca.
Antes:
Depois de:
Na implementação "before", as funções modificaram o objeto e terminaram em
return this
. Alterei a implementação para retornar um novo objeto do mesmo tipo .Meu raciocínio para essa mudança :
O valor de retorno não tinha nada a ver com a função, estava puramente lá para apoiar a parte do encadeamento. Deveria ter sido uma função nula de acordo com a OOP.
O encadeamento de métodos nas bibliotecas do sistema também o implementa dessa maneira (como linq ou string):
O objeto original permanece intacto, permitindo que o usuário da API decida o que fazer com ele. Permite:
Uma implementação de cópia também pode ser usada para criar objetos:
Onde a
setBackground(color)
função altera a instância e não retorna nada (como deveria) .O comportamento das funções é mais previsível (ver pontos 1 e 2).
O uso de um nome curto de variável também pode reduzir a confusão de códigos, sem forçar uma API no modelo.
Conclusão:
Na minha opinião, uma interface fluente que usa uma
return this
implementação está errada.fonte
return this
gerenciar ou não. É minha opinião que ele obtém uma API fluente de uma maneira "não natural". (Adicionado razão 6: para mostrar uma alternativa não-fluente, que não tem a sobrecarga / adicionou-funcionalidade)O ponto totalmente esquecido aqui é que o encadeamento de métodos permite o DRY . É um substituto eficaz para o "com" (que é mal implementado em alguns idiomas).
Isso importa pela mesma razão que DRY sempre importa; se A for um erro e essas operações precisarem ser executadas em B, você só precisará atualizar em um local, não em 3.
Pragmaticamente, a vantagem é pequena nesse caso. Ainda assim, um pouco menos de digitação, um pouco mais robusto (DRY), eu aceito.
fonte
Eu geralmente odeio o encadeamento de métodos porque acho que piora a legibilidade. A compacidade geralmente é confundida com legibilidade, mas eles não são os mesmos termos. Se você fizer tudo em uma única declaração, isso é compacto, mas é menos legível (mais difícil de seguir) na maioria das vezes do que em várias declarações. Como você notou, a menos que não possa garantir que o valor de retorno dos métodos usados seja o mesmo, o encadeamento de métodos será uma fonte de confusão.
1.)
vs
2.)
vs
3.)
vs
Como você pode ver, você ganha quase nada, porque você precisa adicionar quebras de linha à sua declaração única para torná-la mais legível e adicionar recuo para deixar claro que está falando de objetos diferentes. Bem, se eu quiser usar uma linguagem baseada em identificação, aprenderia Python em vez de fazer isso, sem mencionar que a maioria dos IDEs removerá o recuo, formatando automaticamente o código.
Eu acho que o único lugar em que esse tipo de encadeamento pode ser útil é canalizar fluxos na CLI ou Juntar várias consultas no SQL. Ambos têm um preço para várias instruções. Mas se você quiser resolver problemas complexos, acabará mesmo com aqueles que pagam o preço e escrevem o código em várias instruções usando variáveis ou escrevendo scripts bash e procedimentos ou visualizações armazenados.
Como nas interpretações DRY: "Evite a repetição de conhecimento (não a repetição de texto)". e "Digite menos, nem repita textos.", o primeiro o que o princípio realmente significa, mas o segundo é um mal-entendido comum, porque muitas pessoas não conseguem entender besteiras complicadas como "Todo conhecimento deve ter um único e inequívoco, representação autorizada dentro de um sistema ". O segundo é a compactação a todo custo, que quebra neste cenário, porque piora a legibilidade. A primeira interpretação é interrompida pelo DDD quando você copia o código entre contextos limitados, porque o acoplamento flexível é mais importante nesse cenário.
fonte
O bom:
O mal:
Geral:
Uma boa abordagem é não usar encadeamento em geral até que surjam situações ou módulos específicos sejam particularmente adequados a ele.
O encadeamento pode prejudicar bastante a legibilidade em alguns casos, especialmente quando se pesa nos pontos 1 e 2.
Por acusação, pode ser mal utilizado, como em vez de outra abordagem (passar uma matriz por exemplo) ou misturar métodos de maneiras bizarras (parent.setSomething (). GetChild (). SetSomething (). GetParent (). SetSomething ()).
fonte
Resposta opinativa
A maior desvantagem do encadeamento é que pode ser difícil para o leitor entender como cada método afeta o objeto original, se isso ocorrer, e que tipo cada método retorna.
Algumas perguntas:
A depuração, na maioria dos idiomas, pode realmente ser mais difícil com o encadeamento. Mesmo que cada etapa da cadeia esteja em sua própria linha (o que meio que derrota o propósito do encadeamento), pode ser difícil inspecionar o valor retornado após cada etapa, especialmente para métodos sem mutação.
Os tempos de compilação podem ser mais lentos, dependendo do idioma e do compilador, pois as expressões podem ser muito mais complexas de resolver.
Acredito que, como em tudo, o encadeamento é uma boa solução que pode ser útil em alguns cenários. Deve ser usado com cautela, entendendo as implicações e limitando o número de elementos da cadeia a alguns.
fonte
Nas linguagens digitadas (que faltam
auto
ou equivalentes), isso evita que o implementador declare os tipos de resultados intermediários.Para cadeias mais longas, você pode estar lidando com vários tipos intermediários diferentes, é necessário declarar cada uma delas.
Acredito que essa abordagem tenha sido realmente desenvolvida em Java, onde a) todas as chamadas de função são invocações de função de membro eb) tipos explícitos são necessários. É claro que há uma troca aqui em termos, perdendo algumas explicações, mas em algumas situações algumas pessoas acham que vale a pena.
fonte