Encadeamento de métodos - por que é uma boa prática ou não?

151

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.

Ilari Kajaste
fonte
Parece que o encadeamento de métodos é pelo menos uma maneira de escrever código inteligente em java. Mesmo se não cada um concorda ..
Mercer Traieste
Eles também chamam esses métodos "fluentes" ou uma interface "fluente". Você pode atualizar seu título para usar este termo.
295/09 S.Lott
4
Em outra discussão do SO, foi dito que a interface fluente é um conceito maior que tem a ver com a legibilidade do código, e o encadeamento de métodos é apenas uma maneira de visar isso. No entanto, eles estão intimamente relacionados, então adicionei a tag e as interfaces fluentes referenciadas no texto - acho que são suficientes.
9119 Ilari Kajaste
14
A maneira como cheguei a pensar nisso é que o encadeamento de métodos é, de fato, um truque para obter um recurso ausente na sintaxe da linguagem. Realmente não seria necessário se houvesse uma notação alternativa .interna que ignorasse os valores de retorno do método e sempre invocasse métodos encadeados usando o mesmo objeto.
Ilari Kajaste 07/07
É um ótimo idioma de codificação, mas, como todas as grandes ferramentas, é abusado.
Martin Spamer

Respostas:

74

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:

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

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.

Vilx-
fonte
4
Na IMO, a melhor maneira de usar o encadeamento de métodos nesse cenário é criar um objeto de parâmetro a ser passado para a função, como 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!
pedromanoel 12/06
20
Apenas meu centavo de contribuição sobre os padrões, o método de fábrica geralmente possui apenas um ponto de criação e os produtos são uma escolha estática com base nos parâmetros do método de fábrica. Os olhares de criação de cadeia mais para um padrão Builder onde você chamar métodos diferentes para obter um resultado, os métodos podem ser opcionais como no encadeamento método , podemos ter algo parecido com PizzaBuilder.AddSauce().AddDough().AddTopping()mais referências aqui
Marco Medrano
3
O encadeamento de métodos (como mostrado na pergunta original) é considerado ruim quando viola a lei do demeter . Veja: ifacethoughts.net/2006/03/07/… A resposta dada aqui segue de fato essa lei por ser um "padrão de construtor".
Anjo O'Sphere
2
@Marco Medrano O exemplo do PizzaBuilder sempre me incomodou desde que o li no JavaWorld há séculos. Acho que devo adicionar molho à minha Pizza, não ao meu chef.
Breandán Dalton 28/02
1
Eu sei o que você quer dizer, Vilx. Porém, quando li list.add(someItem), li como "esse código está sendo adicionado someItemao listobjeto". então, quando leio PizzaBuilder.AddSauce(), leio isso naturalmente como "esse código está adicionando molho ao PizzaBuilderobjeto". Em outras palavras, vejo o orquestrador (aquele que está adicionando) como o método no qual o código list.add(someItem)ou PizzaBuilder.addSauce()está incorporado. Mas acho que o exemplo do PizzaBuilder é um pouco artificial. Seu exemplo de MyObject.SpecifySomeParameter(asdasd)funciona bem para mim.
Breandán Dalton 28/02
78

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.

RAIO
fonte
4
O uso de novas linhas e o encadeamento de métodos não são independentes? Você pode encadear com cada chamada em uma nova linha, conforme a resposta @ Vilx, e normalmente pode colocar várias instruções separadas na mesma linha (por exemplo, usando ponto e vírgula em Java).
Brabster
2
Essa resposta é totalmente justificada, mas apenas mostra uma fraqueza presente em todos os depuradores que conheço e não está relacionada especificamente à pergunta.
Masterxilo # 9/14
1
@Brabster. Eles podem ser separados para determinados depuradores, no entanto, ter chamadas separadas com variáveis ​​intermediárias ainda oferece uma riqueza muito maior de informações enquanto você investiga bugs.
RAY
4
+1, em nenhum momento você sabe o que cada método retornou ao depurar uma etapa da cadeia de métodos. O padrão da cadeia de métodos é um hack. É o pior pesadelo de um programador de manutenção.
Lee Kowalkowski 4/03/15
1
Também não sou muito fã de encadeamento, mas por que não colocar simplesmente o ponto de interrupção nas definições do método?
Pankaj Sharma
39

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.

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

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:

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

... 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.

Brian Moeskau
fonte
Se houver chance de a Participantnão ter um válido Schedule, o getSchedulemétodo é projetado para retornar um Maybe(of Schedule)tipo e o saveTométodo é projetado para aceitar um Maybetipo.
Lightman
24

Martin Fowler tem uma boa discussão aqui:

Encadeamento de método

Quando usá-lo

O encadeamento de métodos pode acrescentar muito à legibilidade de uma DSL interna e, como resultado, tornou-se quase um sinônimo para DSLs internas em algumas mentes. No entanto, o encadeamento de métodos é melhor quando usado em conjunto com outras combinações de funções.

O encadeamento de métodos é particularmente eficaz com gramáticas como parent :: = (this | that) *. O uso de métodos diferentes fornece uma maneira legível de ver qual argumento está chegando. Da mesma forma, argumentos opcionais podem ser facilmente ignorados com o Method Chaining. Uma lista de cláusulas obrigatórias, como pai :: = primeiro segundo, não funciona tão bem com o formulário básico, embora possa ser bem suportada usando interfaces progressivas. Na maioria das vezes, eu preferiria a função aninhada para esse caso.

O maior problema para o encadeamento de métodos é o problema de acabamento. Embora existam soluções alternativas, geralmente se você se deparar com isso, é melhor usar uma Função Aninhada. Função aninhada também é uma escolha melhor se você estiver se metendo em confusão com as variáveis ​​de contexto.

Dirk Vollmar
fonte
2
O link está inoperante :(
Qw3ry 15/08
O que você quer dizer com DSL? Linguagem específica de domínio
Sören
@ Sören: Fowler refere-se a idiomas específicos do domínio.
perfil completo de Dirk Vollmar
21

Na minha opinião, o encadeamento de métodos é uma novidade. Claro, parece legal, mas não vejo vantagens reais nele.

Como é:

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

melhor que:

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

A exceção pode ser quando addObject () retorna um novo objeto. Nesse caso, o código desencadeado pode ser um pouco mais complicado, como:

someList = someList.addObject("str1")
someList = someList.addObject("str2")
someList = someList.addObject("str3")
Tom Dalling
fonte
9
É mais conciso, pois você evita duas das partes 'someList', mesmo no seu primeiro exemplo, e termina com uma linha em vez de três. Agora, se isso é realmente bom ou ruim, depende de coisas diferentes e talvez seja uma questão de gosto.
Fabian Steeg
26
A verdadeira vantagem de ter 'someList' apenas uma vez é que é muito mais fácil atribuir um nome mais longo e mais descritivo. Sempre que um nome precisa aparecer várias vezes em rápida sucessão, há uma tendência a abreviá-lo (para reduzir a repetição e melhorar a legibilidade), o que o torna menos descritivo, prejudicando a legibilidade.
Chris Dodd
Um bom argumento sobre legibilidade e princípios DRY com a ressalva de que o encadeamento de métodos impede a introspecção do valor de retorno de um determinado método e / ou implica uma presunção de listas / conjuntos vazios e valores não nulos de todos os métodos da cadeia (uma falácia causando muitos NPEs em sistemas, tenho que depurar / corrigir com frequência).
Darrell Teague
@ChrisDodd Nunca ouviu falar de conclusão automática?
Inf3rno 16/02/19
1
"o cérebro humano é muito bom em reconhecer a repetição de texto se tiver o mesmo recuo" - esse é precisamente o problema da repetição: faz com que o cérebro se concentre no padrão de repetição. Então, para ler E COMPREENDER o código, você deve forçar seu cérebro a olhar além / atrás do padrão de repetição para ver o que realmente está acontecendo, que está nas diferenças entre o padrão de repetição que seu cérebro tende a encobrir. É por isso que a repetição é tão ruim para a legibilidade do código.
Chris Dodd
7

É 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.

rprandi
fonte
7

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.

aberrant80
fonte
6

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)

John Nicholas
fonte
De fato, pode ser subjetivo, daí a marca subjetiva. O que espero que as respostas me realcem é que, em casos em que o encadeamento de métodos seria uma boa idéia - no momento não vejo muito, mas acho que esse é apenas o meu fracasso em entender os pontos positivos do conceito, em vez de algo inerentemente ruim no próprio encadeamento.
9119 Ilari Kajaste
Não seria inerentemente ruim se resultar em alto acoplamento? Quebrar a cadeia em declarações individuais não diminui a legibilidade.
aberrant80
1
depende de quão dogmático você quer ser. Se resultar em algo mais legível, isso pode ser preferível em várias situações. O grande problema que tenho com essa abordagem é que a maioria dos métodos em um objeto retornará referências ao próprio objeto, mas geralmente o método retornará uma referência a um objeto filho no qual você pode encadear mais métodos. Quando você começa a fazer isso, fica muito difícil para outro codificador desvendar o que está acontecendo. Além disso, qualquer alteração na funcionalidade de um método será um problema para depurar em uma instrução composta grande.
10139 John Nicholas
6

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

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

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:

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

Mas isso não é muito mais fácil de ler:

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

Ou o que acontece com um aluno?

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

vs

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

É 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 como

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

Vs algo como

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)

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.

  1. Tente não encadear entre classes
  2. Faça rotinas especificamente para encadeamento
  3. Faça apenas UMA coisa em uma rotina de encadeamento
  4. Use-o quando melhorar a legibilidade
  5. Use-o quando simplificar o código
Apeiron
fonte
6

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:

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

Essas regras podem ser implementadas usando essas 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();
}

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:

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()

Outro bom exemplo é o jRTF , um pouco de DSL projetado para analisar documentos RTF diretamente em Java. Um exemplo:

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 );
Lukas Eder
fonte
@ user877329: Sim, ele pode ser usado em praticamente qualquer linguagem de programação orientada a objeto que sabe algo como interfaces e polimorfismo de subtipo
Lukas Eder
4

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 :

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

Isso parece muito mais limpo (e faz mais sentido, na minha opinião) do que:

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

É realmente subjetivo; Todos tem sua própria opinião.

Nathan
fonte
Este é um exemplo de uma interface fluente com encadeamento de métodos, portanto o UseCase é um pouco diferente aqui. Você não está apenas encadeando, mas está criando uma linguagem específica de domínio interno que lê com facilidade. Em uma nota lateral, o ActiveRecord do IC não é um ActiveRecord.
Gordon
3

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.

Shane Thorndike
fonte
1
+1 em "a falácia primária é 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". O caso de uso de destaque é para manipulações sem estado em objetos (onde, em vez de alterar seu estado, você retorna um novo objeto no qual continua a agir). As perguntas e outras respostas do OP mostram ações estatais que de fato parecem estranhas ao encadeamento.
OmerB
Sim, você está certo, é uma manipulação sem estado, exceto que normalmente eu não criei um novo objeto, mas use a injeção de dependência para torná-lo um serviço disponível. E sim, casos de uso com estado não são para o que eu acho que o encadeamento de métodos foi planejado. A única exceção que vejo é se você inicializar o serviço de DI com algumas configurações e tiver algum tipo de cão de guarda para monitorar o estado como um serviço COM de algum tipo. Apenas IMHO.
Shane Thorndike
2

Concordo que mudei a maneira como uma interface fluente foi implementada na minha biblioteca.

Antes:

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

Depois de:

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

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 :

  1. 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.

  2. O encadeamento de métodos nas bibliotecas do sistema também o implementa dessa maneira (como linq ou string):

    myText = myText.trim().toUpperCase();
    
  3. O objeto original permanece intacto, permitindo que o usuário da API decida o que fazer com ele. Permite:

    page1 = collection.limit(10);
    page2 = collection.offset(10).limit(10);
    
  4. Uma implementação de cópia também pode ser usada para criar objetos:

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

    Onde a setBackground(color)função altera a instância e não retorna nada (como deveria) .

  5. O comportamento das funções é mais previsível (ver pontos 1 e 2).

  6. 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.

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

Conclusão:
Na minha opinião, uma interface fluente que usa uma return thisimplementação está errada.

Bob Fanger
fonte
1
Mas o retorno de uma nova instância para cada chamada criaria um pouco de sobrecarga, especialmente se você estiver usando itens maiores? Pelo menos para idiomas não gerenciados.
Apeiron
@Apeiron Em termos de desempenho, é definitivamente mais rápido de return thisgerenciar 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)
Bob Fanger
Eu concordo com você. É melhor deixar o estado subjacente de uma DSL intocado e retornar novos objetos a cada chamada de método. Estou curioso: o que é essa biblioteca que você mencionou?
Lukas Eder
1

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).

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

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

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.

Anfurny
fonte
14
Repetir um nome de variável no código fonte não tem nada a ver com o princípio DRY. DRY afirma que "todo conhecimento deve ter uma representação única, inequívoca e autoritária dentro de um sistema" ou, em outros termos, evitar a repetição do conhecimento (não a repetição do texto ).
Pedromanoel
4
Certamente é a repetição que viola a seco. Repetir nomes de variáveis ​​(desnecessariamente) causa todo o mal da mesma maneira que outras formas de seca: Cria mais dependências e mais trabalho. No exemplo acima, se renomearmos A, a versão úmida precisará de 3 alterações e causará erros na depuração se algum dos três estiver faltando.
Anfurny
1
Não consigo ver o problema que você apontou neste exemplo, pois todas as chamadas de método estão próximas uma da outra. Além disso, se você esquecer de alterar o nome de uma variável em uma linha, o compilador retornará um erro e isso será corrigido antes que você possa executar seu programa. Além disso, o nome de uma variável é limitado ao escopo de sua declaração. A menos que essa variável seja global, o que já é uma prática ruim de programação. OMI, DRY não se trata de menos digitação, mas de manter as coisas isoladas.
Pedromanoel
O "compilador" pode retornar um erro, ou talvez você esteja usando PHP ou JS e o interpretador só pode emitir um erro em tempo de execução quando você atinge essa condição.
Anfurny 10/07/12
1

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.)

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

vs

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

2.)

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

vs

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

3.)

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

vs

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

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.

inf3rno
fonte
0

O bom:

  1. É conciso, mas permite que você coloque mais em uma única linha com elegância.
  2. Às vezes, você pode evitar o uso de uma variável, que pode ser útil ocasionalmente.
  3. Pode ter um desempenho melhor.

O mal:

  1. Você está implementando retornos, essencialmente adicionando funcionalidade a métodos em objetos que não fazem parte do que esses métodos devem fazer. Está retornando algo que você já tem puramente para salvar alguns bytes.
  2. Oculta as alternâncias de contexto quando uma cadeia leva a outra. Você pode obter isso com getters, exceto que é bem claro quando o contexto muda.
  3. O encadeamento em várias linhas parece feio, não funciona bem com indentação e pode causar confusão ao operador (especialmente em idiomas com ASI).
  4. Se você deseja começar a retornar outra coisa útil para um método encadeado, provavelmente terá mais dificuldade em corrigi-lo ou encontrar mais problemas com ele.
  5. Você está transferindo o controle para uma entidade para a qual normalmente não seria transferido apenas por conveniência, mesmo em idiomas estritamente tipificados, erros causados ​​por isso nem sempre podem ser detectados.
  6. Pode ter um desempenho pior.

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 ()).

jgmjgm
fonte
0

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:

  • Os métodos na cadeia retornam um novo objeto ou o mesmo objeto foi alterado?
  • Todos os métodos da cadeia retornam o mesmo tipo?
  • Caso contrário, como é indicado quando um tipo na cadeia é alterado?
  • O valor retornado pelo último método pode ser descartado com segurança?

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.

Eneko Alonso
fonte
0

Nas linguagens digitadas (que faltam autoou equivalentes), isso evita que o implementador declare os tipos de resultados intermediários.

import Participant
import Schedule

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

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.

Dave
fonte