É um cheiro de código se um objeto conhece muito de seu proprietário?

9

Em nossa aplicação Delphi 2007, estamos usando muitas das seguintes construções

FdmBasic:=TdmBasicData(FindOwnerClass(AOwner,TdmBasicData));

O FindOwnerClass percorre a hierarquia do proprietário do componente atual para cima para encontrar uma classe específica (no exemplo TdmBasicData). O objeto resultante é armazenado na variável de campo FdmBasic. Usamos isso principalmente para repassar os módulos de dados.

Exemplo: ao gerar um relatório, os dados resultantes são compactados e armazenados em um campo Blob de uma tabela acessada por meio de um módulo de dados TdmReportBaseData. Em um módulo separado do nosso aplicativo, há funcionalidade para mostrar os dados do relatório em um formulário Paginado usando o ReportBuilder. O código principal deste módulo (TdmRBReport) usa uma classe TRBTempdatabase para converter os dados compactados do blob em diferentes tabelas que são utilizáveis ​​no Reportbuilder runtime reportdesigner. O TdmRBReport tem acesso ao TdmReportBaseData para todos os tipos de dados relacionados ao relatório (tipo de relatório, configurações de cálculos de relatório etc.). TRBTempDatabase é construído em TdmRBReport, mas precisa ter acesso a TdmReportBasedata. Portanto, isso agora é feito usando a construção acima:

constructor TRBTempDatabase.Create(aOwner: TComponent);
begin
  inherited Create(aOwner);

  FdmReportBaseData := TdmRBReport(FindOwnerClass(Owner, TdmRBReport)).dmReportBaseData;
end;{- .Create }

Meu sentimento é que isso significa que o TRBTempDatabase conhece muito de seu proprietário, e eu queria saber se isso é algum tipo de cheiro de código ou Anti-padrão.

Quais são seus pensamentos sobre isso? Isso é um cheiro de código? Se sim, qual é a melhor maneira?

Bascy
fonte
11
Se ele soubesse muito sobre outra classe, teria sido uma maneira mais fácil de fazê-lo.
Loren Pechtel

Respostas:

13

Parece um padrão de localizador de serviço que foi descrito pela primeira vez por Martin Fowler (que foi identificado como um antipadrão comum).

A Injeção de Dependência baseada na construção é preferida a um Localizador de Serviço, pois promove a visibilidade dos parâmetros requeridos e promove Testes de Unidade mais simples.

O problema do uso de um Localizador de Serviços não é que você dependa de uma implementação específica do Localizador de Serviços (embora isso também possa ser um problema), mas é um antipadrão de boa-fé. Isso proporcionará aos consumidores de sua API uma experiência horrível para desenvolvedores e piorará sua vida como desenvolvedor de manutenção, pois você precisará usar quantidades consideráveis ​​de capacidade cerebral para entender as implicações de todas as alterações feitas.

O compilador pode oferecer ajuda tanto aos consumidores quanto aos produtores quando a Injeção de Construtor é usada, mas nenhuma assistência está disponível para APIs que dependem do Localizador de Serviço.

Também notavelmente também quebra a Lei de Deméter

A Lei de Demeter (LoD) ou Princípio do Menos Conhecimento é uma diretriz de design para o desenvolvimento de software, particularmente programas orientados a objetos. Em sua forma geral, o LoD é um caso específico de acoplamento solto.

A Lei de Deméter para funções exige que um método M de um objeto O possa invocar apenas os métodos dos seguintes tipos de objetos:

  1. O próprio
  2. Parâmetros de M
  3. quaisquer objetos criados / instanciados dentro de M
  4. Objetos de componentes diretos de O
  5. uma variável global, acessível por O, no escopo de M

Em particular, um objeto deve evitar chamar métodos de um objeto membro retornado por outro método. Para muitas linguagens modernas orientadas a objetos que usam um ponto como identificador de campo, a lei pode ser declarada simplesmente como "use apenas um ponto". Ou seja, o código abMethod () infringe a lei onde a.Method () não. Como um exemplo simples, quando alguém quer passear com um cachorro, seria loucura ordenar que as pernas do cachorro passassem diretamente; em vez disso, comanda-se o cão e deixa-o cuidar das próprias pernas.

A melhor maneira

Efetivamente, a melhor maneira é remover a chamada do localizador de serviço dentro da classe e passar o proprietário correto como um parâmetro dentro de seu construtor. Mesmo que isso signifique que você tem uma classe de serviço que executa uma consulta de proprietário e a passa ao construtor da classe

constructor TRBTempDatabase.Create(aOwner: TComponent, ownerClass: IComponent);
begin
  inherited Create(aOwner);

  FdmReportBaseData := TdmRBReport(ownerClass).dmReportBaseData;
end;{- .Create }
Justin Shield
fonte
3
Grande resposta, e adereços para quem veio com esta maravilhosa analogia:As a simple example, when one wants to walk a dog, it would be folly to command the dog's legs to walk directly; instead one commands the dog and lets it take care of its own legs.
Andy Hunt
3

Uma das dificuldades em fazer com que os objetos filhos saibam demais sobre o pai é que você acaba implementando padrões que podem (e geralmente o fazem) ficar muito acoplados, o que cria grandes dores de cabeça por dependência e muitas vezes depois se torna muito difícil modificar e manter com segurança mais tarde.

Dependendo de quão profundamente suas duas classes estão conectadas, parece um pouco com a descrição de Fowler do código de inveja de recursos ou odores de código de intimidade inadequada.

Parece haver a necessidade de carregar ou ler uma classe com dados; nesse caso, você pode usar vários padrões alternativos para quebrar a dependência entre a criança e sua cadeia de pais, e parece que você precisa delegar a tarefa de acessar sua classe de dados em vez de tornar a classe do acessador de dados responsável por fazer tudo sozinho.

S.Robins
fonte