De acordo com a lei de Demeter, é permitido a uma classe devolver um de seus membros?

13

Eu tenho três perguntas sobre a lei de Deméter.

Além das classes que foram especificamente designadas para retornar objetos - como as classes factory e builder -, é aceitável que um método retorne um objeto, por exemplo, um objeto mantido por uma das propriedades da classe ou que viole a lei do demeter (1) ? E se viola a lei do demeter, importaria se o objeto retornado fosse um objeto imutável que represente um dado e não contenha nada além de getters para esses dados (2a)? Ou esse ValueObject é um antipadrão, porque tudo o que é feito com os dados dessa classe é feito fora da classe (2b)?

No pseudo-código:

class A {}

class B {

    private A a;

    public A getA() {
        return this.a;
    }
}

class C {

    private B b;
    private X x;

    public void main() {
        // Is it okay for B.getA to exist?
        A a = this.b.getA();

        a.doSomething();

        x.doSomethingElse(a);
    }
}

Suspeito que a lei de Deméter proíba um padrão como o acima. O que posso fazer para garantir que isso doSomethingElse()possa ser chamado sem violar a lei (3)?

user2180613
fonte
9
Muitas pessoas (programadores funcionais em particular) veem a Lei de Demeter como um antipadrão. Outros a veem como a "Sugestão ocasionalmente útil de Deméter".
David Hammen
1
Vou aprovar esta questão, pois ilustra os absurdos da Lei de Demeter. Sim, LoD é "ocasionalmente útil", mas geralmente é estúpido.
user949300
Como está xsendo definido?
candied_orange
@CandiedOrange xé apenas uma propriedade diferente cujo valor é apenas um objeto que é capaz de invocar doSomethingElse.
user2180613
1
Seu exemplo não viola LOD. LOD trata-se de impedir que os clientes de um objeto se acoplem aos detalhes ou colaboradores internos desse objeto. Você quer evitar coisas assim user.currentSession.isLoggedIn(). porque une clientes usernão apenas usermas também seu colaborador session,. Em vez disso, você deseja escreveruser.isLoggedIn() . isso geralmente é alcançado adicionando um isLoggedInmétodo ao userqual a implementação delega currentSession.
Jonah #

Respostas:

7

Em muitas linguagens de programação, todos os valores retornados são objetos. Como outros já disseram, não poder usar os métodos de objetos retornados obriga a nunca retornar nada. Você deve se perguntar: "Quais são as responsabilidades das classes A, B e C?" É por isso que o uso de nomes de variáveis ​​metassintáticas como A, B e C sempre solicita a resposta "depende" porque não há responsabilidades herdadas nesses termos.

Você pode querer examinar o Design Orientado a Domínio, que fornecerá um conjunto mais detalhado de heurísticas para refletir sobre onde a funcionalidade deve ir e quem deve estar chamando o quê.

Sua segunda pergunta sobre objetos imutáveis ​​fala da noção de um
objeto de valor comparado a um objeto de entidade. no DDD, você pode transmitir objetos de valor quase sem restrições. Isso não é verdadeiro para objetos de entidade.

O LOD simplista é muito melhor expresso em DDD como as regras para acessar agregados . Nenhum objeto externo deve conter referências a membros de agregados. Somente uma referência raiz deve ser mantida.

Além do DDD, você pelo menos deseja desenvolver seus próprios conjuntos de estereótipos para as classes que são razoáveis ​​para o seu domínio. Aplique regras sobre esses estereótipos ao criar seu sistema.

Lembre-se também sempre de que todas essas regras são para gerenciar a complexidade, não para prejudicar a si mesmo.

Jeremy
fonte
1
O DDD fornece regras sobre quando membros da classe, como dados, podem ser expostos (por exemplo, getters devem existir)? Todas as discussões em torno Law of Demeter, tell, don't ask, getters are evil, object encapsulatione single responsibility principleparecem resumir-se a esta pergunta: quando deve delegação para um objeto de domínio ser usado ao contrário de começar o valor do objeto e fazer algo com ele? Seria muito útil poder aplicar qualificações diferenciadas a situações em que essa decisão deve ser tomada.
user2180613
Existem diretrizes como "getters são maus" porque muitas pessoas tratam instâncias de classe como estruturas de dados (um conjunto de dados a serem passados). Isso não é programação orientada a objetos. Objetos são pacotes de funcionalidades / comportamentos que realmente fornecem algum trabalho. O escopo do trabalho é definido pela responsabilidade. Obviamente, este trabalho criará objetos que são retornados dos métodos, etc. Se sua classe está realmente fazendo um trabalho significativo que você pode definir claramente além de "representar o objeto de dados X com os atributos Y e Z", então você está no caminho certo . Eu não me estressaria com isso.
Jeremy
Para expandir esse ponto, quando você usa muitos getters / askers, isso geralmente significa que você tem algum método grande em algum lugar que está orquestrando tudo, que Fowler chama de script de transação. Antes de começar a usar um getter, pergunte-se ... por que estou pedindo esses dados do objeto? Por que essa funcionalidade não está em um método no objeto? Se não é responsabilidade dessa classe implementar essa funcionalidade, vá em frente e use um getter. O livro [Refatoração] ( martinfowler.com/books/refactoring.html ) de Fowler é um livro sobre heurísticas para essas perguntas.
Jeremy
Em seu livro Implementing Domain-Driven Design, Vaughn Vernon menciona a Lei de Deméter e Tell, Don't Ask, no capítulo 10, pp 382. Pode valer a pena dar uma olhada se você tiver acesso a ela.
Jeremy
6

De acordo com a lei de Demeter, é permitido a uma classe devolver um de seus membros?

Sim, certamente é.

Vejamos os principais pontos :

  • Cada unidade deve ter apenas conhecimento limitado sobre outras unidades: somente unidades "estreitamente" relacionadas à unidade atual.
  • Cada unidade deve conversar apenas com seus amigos; não fale com estranhos.
  • Fale apenas com seus amigos imediatos.

Todos os três permitem que você faça uma pergunta: Quem é um amigo?

Ao decidir o que retornar, a Lei de Demeter ou o princípio do mínimo conhecimento (LoD) não determina que você se defenda contra codificadores que insistem em violá-lo. Ele determina que você não força os codificadores a violá-lo.

Confundir isso é exatamente o motivo pelo qual muitos acham que um levantador deve sempre retornar vazio. Não. Você deve permitir uma maneira de fazer consultas (getters) que não alterem o estado do sistema. Essa é a separação básica da consulta de comando .

Isso significa que você é livre para se aprofundar em uma base de código encadeando o que quiser? Não. Encadeie apenas o que deveria ser encadeado. Caso contrário, a corrente poderá mudar e, de repente, seu material será quebrado. É isso que os amigos querem dizer.

Podem ser projetadas cadeias longas. Interfaces fluentes, iDSL, grande parte do Java8 e o bom e antigo StringBuilder destinam-se a permitir que você construa longas cadeias. Eles não violam o LoD porque tudo na cadeia deve trabalhar juntos e promete continuar trabalhando juntos. Você viola o desmonte quando encadeia coisas que nunca ouviram falar um do outro. Amigos são aqueles que prometeram manter sua rede funcionando. Amigos de amigos não.

Além das classes que foram especificamente designadas para retornar objetos - como as classes factory e builder -, é aceitável que um método retorne um objeto, por exemplo, um objeto mantido por uma das propriedades da classe ou que viole a lei do demeter (1) ?

Isso cria apenas uma oportunidade de violar o demeter. Isto não é uma violação. Isso nem é necessariamente ruim.

E se viola a lei do demeter, importaria se o objeto retornado fosse um objeto imutável que represente um dado e não contenha nada além de getters para esses dados (2)?

Imutável é bom, mas irrelevante aqui. Chegar às coisas através de uma cadeia mais longa não a torna melhor. O que o torna melhor é separar a obtenção do uso. Se você estiver usando, peça o que você precisa como parâmetro. Não vá procurá-lo, mergulhando nos caçadores de estranhos.

No pseudo-código:

Suspeito que a lei de Deméter proíba um padrão como o acima. O que posso fazer para garantir que doSomethingElse () possa ser chamado sem violar a lei (3)?

Antes de falar sobre x.doSomethingElse(a)entender que você escreveu basicamente

b.getA().doSomething()

Agora, LoD não é um exercício de contagem de pontos . Mas quando você cria uma cadeia, está dizendo que sabe como obter um A(usando B) e sabe como usar um A. Bem agora AeB melhor ter amigos íntimos, porque você os juntou.

Se você tivesse acabado de pedir algo para lhe entregar uma Acoisa semelhante, você poderia usar Ae não se importaria de onde veio e Bpoderia viver uma vida feliz e livre de sua obsessão por sair AdeB .

Quanto x.doSomethingElse(a)sem detalhes de ondex veio, o LoD não tem nada a dizer sobre isso.

O LoD pode incentivar a separação do uso da construção . Mas vou apontar que se você tratar religiosamente todos os objetos como não amigáveis, ficará preso escrevendo código em métodos estáticos. Você pode construir um gráfico de objeto maravilhosamente complexo dessa maneira, mas eventualmente precisará chamar um método para começar a trabalhar. Você simplesmente tem que decidir quem são seus amigos. Não há como fugir disso.

Então, sim, uma classe tem permissão para retornar um de seus membros sob LoD. Ao fazê-lo, você deve deixar claro se esse membro é amigável com sua classe, porque, se não for, algum cliente pode tentar acoplá-lo a essa classe usando você para obtê-la antes de usá-la. Isso é importante porque agora o que você retorna sempre deve suportar esse uso.

Existem muitos casos em que isso simplesmente não é uma preocupação. As coleções podem ignorar isso simplesmente porque são feitas para serem amigas de tudo que as usa. Da mesma forma, objetos de valor são amigáveis ​​com todos. Mas se você escreveu um utilitário de validação de endereço que exigia um objeto de funcionário do qual ele extraiu um endereço, em vez de apenas solicitar o endereço, é melhor esperar que o funcionário e o endereço sejam da mesma biblioteca.

candied_orange
fonte
As interfaces fluentes não são uma exceção ao LOD por causa de alguma noção de simpatia ... Em uma interface fluente, cada um dos métodos da cadeia é chamado no mesmo objeto, porque todos retornam a si mesmos. settings.darkTheme().monospaceFont()é apenas açúcar sintático parasettings.darkTheme(); settings.monospaceFont();
Jonah
3
@Jonah Retornar a si mesmo é a derradeira amizade. E nem todas as interfaces fluentes o fazem. Muitos iDSLs também são fluentes e retornam tipos diferentes para alterar os métodos disponíveis em diferentes pontos. Considere o jOOQ , o step builder , o wizard builder e o meu próprio pesadelo monstro builder . Fluente é um estilo. Não é um padrão.
candied_orange
2

Além de classes que foram especificamente designadas para retornar objetos [...]

Não entendo bem esse argumento. Qualquer objeto pode retornar um objeto como resultado de uma chamada de mensagem. Especialmente se você pensa em "tudo como objeto".

As classes, no entanto, são os únicos objetos que podem instanciar novos objetos de seu próprio tipo, enviando a eles a mensagem "nova" (ou frequentemente substituída por uma nova palavra-chave nos idiomas OOP mais comuns)


De qualquer forma, em relação à sua pergunta, eu suspeitaria que um objeto retornasse uma de suas propriedades para ser usado em outro lugar. Pode ser que esse objeto esteja vazando informações sobre seu estado ou detalhes de implementação.

E você não gostaria que o objeto C soubesse sobre os detalhes de implementação de B. Essa é uma dependência indesejada do objeto A da perspectiva do objeto C.

Portanto, você pode se perguntar se C, ao conhecer A, não está sabendo muito sobre o trabalho que ele tem que fazer, independentemente do LOD.

Há casos em que isso pode ser legítimo, por exemplo, solicitar a um objeto de coleção um de seus itens é apropriado e não violar nenhuma regra Demeter. Pedir um objeto Book para seu objeto SQLConnection, por outro lado, é arriscado e deve ser evitado.

O LOD existe porque muitos programadores tendem a solicitar informações aos objetos antes de realizar um trabalho. O LOD existe para nos lembrar que, às vezes (se não sempre), estamos complicando demais as coisas, desejando que nossos objetos façam muito. Às vezes, apenas precisamos pedir a outro objeto que faça o trabalho por nós. O LOD existe para nos fazer pensar duas vezes sobre onde colocamos o comportamento em nossos objetos.

NikoGJ
fonte
0

Além das classes que foram especificamente designadas para retornar objetos - como as classes factory e builder -, é aceitável que um método retorne um objeto?

Por que não seria? Você parece sugerir que é necessário sinalizar para seus colegas programadores que a classe é especificamente destinada a retornar objetos, ou ela não deve retornar objetos. Nos idiomas em que tudo é um objeto, isso limitaria severamente suas opções, pois você não seria capaz de retornar nada.

Robert Harvey
fonte
O exemplo é sobre o implícito b.getA().doSomething()ex.doSomethingElse(b.getA())
user2180613 18/06
A a = b.getA(); a.DoSomething();- de volta a um ponto.
Robert Harvey
2
@ user2180613 - Se você queria perguntar b.getA().doSomething()e x.doSomethingElse(b.getA())deveria ter perguntado explicitamente sobre isso. Tal como está, sua pergunta parece ser sobre this.b.getA().
David Hammen
1
Como Anão é um membro de C, não criado em Ce não fornecido para C, parece que o uso Aem C(de qualquer maneira) viola LoD. Contar pontos não é o objetivo do LoD, é apenas uma ferramenta ilustrativa. É possível converter Driver().getCar().getSteeringWheel().getFrontWheels().toRight()em instruções separadas, cada uma contendo um ponto, mas a violação do LoD ainda está lá. Eu li em muitos posts neste fórum que a execução de ações deve ser delegada ao objeto que implementa o comportamento real, ou seja tell don't ask. Retornar valores parece entrar em conflito com isso.
user2180613
1
Suponho que seja verdade, mas não responde à pergunta.
user2180613
0

Depende do que você está fazendo. Se você expõe um membro da sua classe, quando não é da conta de ninguém que ele seja membro da sua classe ou qual é o tipo desse membro, isso é uma coisa ruim. Se você alterar o tipo desse membro e o tipo de função que está retornando o membro, e todo mundo precisar alterar seu código, isso é mau.

Por outro lado, se a interface da sua classe declarar que ela fornecerá uma instância de uma conhecida classe A, tudo bem. Se você tiver sorte e puder fazer isso, forneça um membro da sua classe, que bom para você. Se você alterou esse membro de A para B, seria necessário reescrever o método que retorna A, obviamente ele precisará construir um e preenchê-lo com os dados disponíveis, em vez de retornar apenas uma linha um membro. A interface da sua classe não seria alterada.

gnasher729
fonte