É uma má prática passar instâncias por várias camadas?

60

No design do meu programa, geralmente chego ao ponto em que tenho que passar instâncias de objetos por várias classes. Por exemplo, se eu tiver um controlador que carrega um arquivo de áudio e o passa para um player, e o player para o playerRunnable, que o transmite novamente em outro lugar etc. Parece meio ruim, mas eu não sabe como evitá-lo. Ou está tudo bem fazer isso?

EDIT: Talvez o exemplo do player não seja o melhor, porque eu poderia carregar o arquivo mais tarde, mas em outros casos isso não funciona.

Puckl
fonte

Respostas:

54

Como outros já mencionaram, isso não é necessariamente uma prática ruim, mas você deve prestar atenção para não quebrar a separação de preocupações das camadas e passar instâncias específicas de cada camada. Por exemplo:

  • Os objetos de banco de dados nunca devem ser passados ​​para camadas superiores. Vi programas usando a classe DataAdapter do .NET , uma classe de acesso ao banco de dados, e passando-a para a camada da interface do usuário, em vez de usar o DataAdapter no DAL, criando um DTO ou conjunto de dados e passando isso. O acesso ao banco de dados é o domínio do DAL.
  • Os objetos da interface do usuário devem, obviamente, ser limitados à camada da interface do usuário. Mais uma vez, vi isso violado, tanto com ListBoxes preenchidas com dados do usuário passados ​​para a camada BL, em vez de uma matriz / DTO de seu conteúdo, como (um dos meus favoritos em particular), uma classe DAL que recuperava dados hierárquicos de o banco de dados e, em vez de retornar uma estrutura de dados hierárquica, apenas criou e preencheu um objeto TreeView e o transmitiu de volta à interface do usuário para ser adicionado dinamicamente a um formulário.

No entanto, se as instâncias que você está passando são os DTOs ou as próprias entidades, provavelmente está ok.

Avner Shahar-Kashtan
fonte
11
Isso pode parecer chocante, mas nos primeiros dias sombrios do .NET essa era a prática geralmente recomendada e provavelmente era melhor do que o que a maioria das outras pilhas está fazendo.
precisa saber é o seguinte
11
Discordo. É verdade que a Microsoft endossou a prática de aplicativos de camada única em que o cliente Winforms também acessou o banco de dados e o DataAdapter foi adicionado diretamente ao formulário como um controle invisível, mas essa é apenas uma arquitetura específica, diferente da configuração de camada N do OP . Mas em uma arquitetura multicamada, e isso era verdade para o VB6 / DNA, mesmo antes do .NET, os objetos DB permaneciam na camada DB.
Avner Shahar-Kashtan
Para esclarecer: você viu pessoas acessando a interface do usuário diretamente (no seu exemplo, caixas de listagem) a partir da "Camada de dados"? Eu não acho que me deparei com uma violação no código de produção .. uau ..
Simon Whitehead
7
@SimonWhitehead Exatamente. As pessoas que estavam confusas com a distinção entre um ListBox e uma matriz e usavam o ListBoxes como um DTO. Foi um momento que me ajudou a perceber quantas suposições invisíveis que faço que não são intuitivas para os outros.
Avner Shahar-Kashtan
11
@SimonWhitehead - Sim, eu já vi isso nos programas VB6 e VB.NET Framework 1.1 e 2.0 e fui encarregado de manter esses monstros. Fica muito feio, muito rapidamente.
Jfrankcarr 27/09/12
15

Interessante que ninguém tenha falado sobre objetos imutáveis ainda. Eu argumentaria que passar um objeto imutável por todas as várias camadas é realmente uma coisa boa , em vez de criar muitos objetos de vida curta para cada camada.

Há algumas ótimas discussões sobre imutabilidade por Eric Lippert em seu blog

Por outro lado, eu argumentaria que a passagem de objetos mutáveis ​​entre as camadas é um design ruim . Você está essencialmente construindo uma camada com a promessa de que as camadas adjacentes não a alterarão de maneira a quebrar seu código.

M Afifi
fonte
13

Passar instâncias de objetos é uma coisa normal a se fazer. Reduz a necessidade de manter o estado (ou seja, variáveis ​​de instância) e desacopla o código de seu contexto de execução.

Um problema que você pode enfrentar é a refatoração, quando você deve alterar as assinaturas de vários métodos ao longo da cadeia de chamadas em resposta à alteração dos requisitos de parâmetros de um método próximo à parte inferior dessa cadeia. No entanto, isso pode ser mitigado com o uso de modernas ferramentas de desenvolvimento de software que ajudam na refatoração.

dasblinkenlight
fonte
6
Eu diria que outro problema que você pode enfrentar envolve imutabilidade. Lembro-me de um bug muito desconcertante em um projeto em que trabalhei onde um desenvolvedor modificou um DTO sem pensar no fato de que sua classe em particular não era a única com referência a esse objeto.
Phil
8

Talvez menor, mas existe o risco de atribuir essa referência em algum lugar de uma das camadas, potencialmente causando uma referência pendente ou vazamento de memória posteriormente.

techfoobar
fonte
Seu ponto de vista está correto, mas pela terminologia do OP ("passando instâncias de objetos"), sinto que ele está passando valores (não ponteiros) ou se está falando de um ambiente de coleta de lixo (Java, C #, Python, Go,. ..)
Mohammad Dehghan
7

Se você estiver passando objetos simplesmente porque é necessário em uma área remota do seu código, o uso da inversão dos padrões de design de controle e injeção de dependência, juntamente com, opcionalmente, um contêiner IoC apropriado pode resolver problemas de transporte de instâncias de objetos. Eu o usei em um projeto de tamanho médio e nunca mais consideraria escrever um grande pedaço de código do servidor sem usá-lo.

Steven Schlansker
fonte
Parece interessante, eu já uso injeção de construtor e acho que meus componentes de alto nível controlam os componentes de baixo nível. Como você usa um contêiner IOC para evitar carregar instâncias?
Puckl
Acho que encontrei a resposta aqui: martinfowler.com/articles/injection.html
Puckl
11
Nota lateral, se você estiver trabalhando em Java, o Guice é realmente bom, e você pode escopo suas ligações para coisas como solicitações, para que o componente de alto nível esteja criando o escopo e vinculando as instâncias às classes corretas naquele momento.
Dave
4

Passar dados através de várias camadas não é uma coisa ruim, é realmente a única maneira que um sistema em camadas pode funcionar sem violar a estrutura em camadas. O sinal de que há problemas é quando você está passando seus dados para vários objetos na mesma camada para atingir seu objetivo.

Ryathal
fonte
3

Resposta rápida: Não há nada errado em passar instâncias de objetos. Como também mencionado, o ponto é pular a atribuição dessa referência em todas as camadas, potencialmente causando uma referência pendente ou vazamentos de memória.

Em nossos projetos, usamos essa prática para passar DTOs (objeto de transferência de dados) entre camadas e é uma prática muito útil. Também reutilizamos nossos objetos dto para construir uma vez mais complexos uma vez, como para informações resumidas.

EL Yusubov
fonte
3

Sou principalmente um desenvolvedor de interface do usuário da Web, mas me parece que seu desconforto intuitivo pode ser menos sobre a passagem da instância e mais sobre o fato de você estar um pouco processual com esse controlador. Seu controlador deveria suar todos esses detalhes? Por que ele faz referência a mais do que o nome de outro objeto para reproduzir o áudio?

No design de POO, costumo pensar em termos do que é sempre-verde e do que é mais provável que esteja sujeito a alterações. O assunto para mudar as coisas é o que você vai querer colocar em suas caixas de objetos maiores, para que você possa manter interfaces consistentes mesmo quando os jogadores mudam ou novas opções são adicionadas. Ou você deseja trocar objetos ou componentes de áudio por atacado.

Nesse caso, seu controlador precisa identificar que é necessário reproduzir um arquivo de áudio e, em seguida, ter uma maneira consistente / sempre-verde de reproduzi-lo. O material do reprodutor de áudio, por outro lado, pode mudar facilmente conforme a tecnologia e as plataformas são alteradas ou novas opções são adicionadas. Todos esses detalhes devem ficar embaixo da interface de um objeto composto maior, o IMO, e você não precisa reescrever seu controlador quando os detalhes de como o áudio é reproduzido mudam. Então, quando você passa uma instância de objeto com os detalhes, como a localização do arquivo, para o objeto maior, toda a troca é feita no interior de um contexto apropriado, onde é menos provável que alguém faça algo bobo com ela.

Portanto, neste caso, não acho que seja a instância do objeto sendo lançada que possa estar incomodando você. É que o capitão Picard está correndo para a sala de máquinas para ligar o núcleo da urdidura, correndo de volta para a ponte para traçar as coordenadas e pressionando o botão "socar" depois de ligar os escudos em vez de simplesmente dizer "Pegue nós ao planeta X na Warp 9. Faça isso. " e deixando sua equipe resolver os detalhes. Porque quando ele lida com isso dessa maneira, ele pode comandar qualquer navio da frota sem conhecer o layout de cada navio e como tudo funciona. E essa é, em última análise, a maior vitória em design de OOP a ser alcançada, a IMO.

Erik Reppen
fonte
2

É um design bastante comum que acaba acontecendo, embora você possa ter problemas de latência se o aplicativo for sensível a esse tipo de coisa.

James
fonte
2

Esse problema pode ser resolvido com variáveis ​​com escopo dinâmico, se o seu idioma as possuir, ou armazenamento local por thread. Esses mecanismos permitem associar algumas variáveis ​​personalizadas a uma cadeia de ativação ou segmento de controle, para que não tenhamos que passar esses valores para um código que não tem nada a ver com eles, apenas para que eles possam ser comunicados a algum outro código o que precisa deles.

Kaz
fonte
2

Como as outras respostas apontaram, esse não é um design inerentemente ruim. Ele pode criar um acoplamento rígido entre as classes aninhadas e as que os aninham, mas afrouxar o acoplamento pode não ser uma opção válida se o aninhamento das referências fornecer um valor ao design.

Uma solução possível é "achatar" as referências aninhadas na classe do controlador.

Em vez de passar um parâmetro várias vezes através de objetos aninhados, você poderia manter na classe da controladora referências a todos os objetos aninhados.

Como exatamente isso é implementado (ou se é uma solução válida) depende do design atual do sistema, como:

  • Você é capaz de manter algum tipo de mapa dos objetos aninhados no controlador sem ficar muito complicado?
  • Quando você passa o parâmetro para o objeto aninhado apropriado, o objeto aninhado pode reconhecê-lo imediatamente ou ocorreu uma funcionalidade adicional ao passá-lo pelos objetos aninhados?
  • etc.

Esse é um problema que encontrei em um padrão de design MVC para um cliente GXT. Nossos componentes da GUI continham componentes da GUI aninhados para várias camadas. Quando os dados do modelo foram atualizados, acabamos passando-o pelas várias camadas até atingir o (s) componente (s) apropriado (s). Ele criou acoplamentos indesejados entre os componentes da GUI porque, se quiséssemos que uma nova classe de componente da GUI aceitasse dados do modelo, teríamos que criar métodos para atualizar os dados do modelo em todos os componentes da GUI que continham a nova classe.

Para corrigi-lo, mantivemos na classe View um mapa de referências a todos os componentes da GUI aninhados para que, sempre que os dados do modelo fossem atualizados, a View pudesse enviar os dados do modelo atualizados diretamente para os componentes da GUI necessários, final da história . Isso funcionou bem porque havia apenas instâncias únicas de cada componente da GUI. Pude ver que não estava funcionando tão bem se houvesse várias instâncias de alguns componentes da GUI, dificultando a identificação de qual cópia precisava ser atualizada.

David Kaczynski
fonte
0

O que você está descrevendo é chamado de padrão de design da Cadeia de Responsabilidade . A Apple usa esse padrão para o sistema de manipulação de eventos, pelo que vale a pena.

user8865
fonte