O princípio Tell Don't Ask diz:
você deve tentar dizer aos objetos o que você quer que eles façam; não faça perguntas sobre seu estado, tome uma decisão e depois diga a eles o que fazer.
O problema é que, como responsável pela chamada, você não deve tomar decisões com base no estado do objeto chamado que resulta na alteração do estado do objeto. A lógica que você está implementando provavelmente é de responsabilidade do objeto chamado, não sua. Para você tomar decisões fora do objeto, viola seu encapsulamento.
Um exemplo simples de "Diga, não pergunte" é
Widget w = ...;
if (w.getParent() != null) {
Panel parent = w.getParent();
parent.remove(w);
}
e a versão Tell é ...
Widget w = ...;
w.removeFromParent();
Mas e se eu precisar saber o resultado do método removeFromParent? Minha primeira reação foi apenas alterar o removeFromParent para retornar um valor booleano se o pai foi removido ou não.
Mas então me deparei com Command Query Separation Pattern que diz NÃO para fazer isso.
Ele afirma que todo método deve ser um comando que executa uma ação ou uma consulta que retorna dados ao chamador, mas não os dois. Em outras palavras, fazer uma pergunta não deve alterar a resposta. Mais formalmente, os métodos devem retornar um valor somente se forem referencialmente transparentes e, portanto, não apresentarem efeitos colaterais.
Esses dois estão realmente em conflito um com o outro e como faço para escolher entre os dois? Eu vou com o programador pragmático ou Bertrand Meyer sobre isso?
Respostas:
Na verdade, seu exemplo de problema já ilustra a falta de decomposição.
Vamos mudar um pouco:
Isso não é realmente diferente, mas torna a falha mais aparente: por que o livro saberia sobre sua prateleira? Simplificando, não deveria. Introduz uma dependência de livros nas prateleiras (o que não faz sentido) e cria referências circulares. Está tudo ruim.
Da mesma forma, não é necessário que um widget conheça seu pai. Você dirá: "Ok, mas o widget precisa de seu pai para se organizar adequadamente etc." e, sob o capô, o widget conhece seu pai e solicita que suas métricas calculem suas próprias métricas com base nelas etc. Segundo dizer, não pergunte se isso está errado. O pai deve dizer a todos os seus filhos para renderizar, passando todas as informações necessárias como argumentos. Dessa forma, pode-se facilmente ter o mesmo widget em dois pais (mesmo que isso faça sentido ou não).
Para voltar ao exemplo do livro - digite o bibliotecário:
Por favor, entenda que não estamos pedindo mais no sentido de dizer, não pergunte .
Na primeira versão, a prateleira era uma propriedade do livro e você solicitou. Na segunda versão, não fazemos nenhuma suposição sobre o livro. Tudo o que sabemos é que o bibliotecário pode nos dizer uma prateleira que contém o livro. Presumivelmente, ele confia em algum tipo de tabela de pesquisa para fazer isso, mas não sabemos ao certo (ele também pode simplesmente percorrer todos os livros em todas as prateleiras) e, na verdade , não queremos saber .
Não confiamos em se ou como a resposta dos bibliotecários está acoplada ao seu estado ou a de outros objetos dos quais possa depender. Dizemos ao bibliotecário para encontrar a prateleira.
No primeiro cenário, codificamos diretamente o relacionamento entre um livro e uma prateleira como parte do estado do livro e o recuperamos diretamente. Isso é muito frágil, porque também assumimos que a estante devolvida pelo livro contém o livro (essa é uma restrição que devemos garantir, caso contrário, talvez não
remove
consigamos tirá-lo da estante).Com a introdução do bibliotecário, modelamos esse relacionamento separadamente, conseguindo assim uma separação de preocupações.
fonte
Se você precisa saber o resultado, então você sabe; essa é sua exigência.
A frase "métodos deve retornar um valor somente se forem referencialmente transparentes e, portanto, não apresentarem efeitos colaterais" é uma boa diretriz a seguir (especialmente se você estiver escrevendo seu programa em um estilo funcional , por simultaneidade ou por outros motivos), mas é não é absoluto.
Por exemplo, pode ser necessário saber o resultado de uma operação de gravação de arquivo (verdadeira ou falsa). Esse é um exemplo de método que retorna um valor, mas sempre produz efeitos colaterais; não há maneira de contornar isso.
Para cumprir a Separação de Comando / Consulta, você teria que executar a operação de arquivo com um método e, em seguida, verificar seu resultado com outro método, uma técnica indesejável, porque desacopla o resultado do método que causou o resultado. E se algo acontecer com o estado do seu objeto entre a chamada do arquivo e a verificação do status?
O ponto principal é o seguinte: se você estiver usando o resultado de uma chamada de método para tomar decisões em outras partes do programa, não estará violando o Tell Don't Ask. Se, por outro lado, você estiver tomando decisões para um objeto com base em uma chamada de método para esse objeto, mova essas decisões para o próprio objeto para preservar o encapsulamento.
Isso não é de todo contraditório com a Separação de Consulta de Comando; de fato, reforça ainda mais, porque não é mais necessário expor um método externo para fins de status.
fonte
Eu acho que você deveria ir com seu instinto inicial. Às vezes, o design deve ser intencionalmente perigoso, para introduzir complexidade imediatamente quando você se comunica com o objeto, para que seja forçado a manipulá-lo corretamente imediatamente, em vez de tentar manipulá-lo no próprio objeto, ocultar todos os detalhes sangrentos e dificultar a limpeza. alterar as premissas subjacentes.
Você deve ter uma sensação de afundamento se um objeto oferecer equivalentes
open
eclose
comportamentos implementados e começar imediatamente a entender com o que está lidando quando vir um valor de retorno booleano para algo que pensou ser uma tarefa atômica simples.Como você lida mais tarde é o seu problema. Você pode criar uma abstração acima dela, com um tipo de widget
removeFromParent()
, mas sempre deve ter um retorno de baixo nível, caso contrário você provavelmente estava fazendo suposições prematuras.Não tente simplificar tudo. Não há nada tão decepcionante quando você confia em algo que parece elegante e inocente, apenas para perceber o verdadeiro horror mais tarde, no pior dos momentos.
fonte
O que você descreve é uma "exceção" conhecida ao princípio de Separação de Consulta de Comando.
Neste artigo, Martin Fowler explica como ele ameaçaria essa exceção.
No seu exemplo, eu consideraria a mesma exceção.
fonte
A separação entre comando e consulta é incrivelmente fácil de entender mal.
Se eu disser no meu sistema, existe um comando que também retorna um valor de uma consulta e você diz "Ha! Você está violando!" você está pulando a arma.
Não, não é isso que é proibido.
O que é proibido é quando esse comando é a ÚNICA maneira de fazer essa consulta. Não. Eu não deveria ter que mudar de estado para fazer perguntas. Isso não significa que tenho que fechar os olhos toda vez que mudo de estado.
Não importa se sim, já que vou abri-las novamente de qualquer maneira. O único benefício para essa reação exagerada foi garantir que os designers não fiquem preguiçosos e se esqueça de incluir a consulta que não muda de estado.
O significado real é tão sutil que é mais fácil para as pessoas preguiçosas dizerem que os levantadores devem retornar nulos. Isso facilita ter certeza de que você não está violando a regra real, mas é uma reação exagerada que pode se tornar uma dor real.
Interfaces fluentes e iDSLs "violam" essa reação exagerada o tempo todo. Há muita energia sendo ignorada se você reagir demais.
Você pode argumentar que um comando deve fazer apenas uma coisa e uma consulta deve fazer apenas uma coisa. E, em muitos casos, esse é um bom argumento. Quem pode dizer que existe apenas uma consulta que deve seguir um comando? Tudo bem, mas isso não é separação de comando-consulta. Esse é o princípio da responsabilidade única.
Olhado desta maneira e um pop de pilha não é uma exceção bizarra. É uma responsabilidade única. O pop só viola a separação da consulta de comando se você esquecer de fornecer uma espiada.
fonte