É problemático ter uma dependência entre objetos da mesma camada em uma arquitetura de software em camadas?

12

Considerando um software de tamanho médio com arquitetura de camada n e injeção de dependência, sinto-me confortável em dizer que um objeto pertencente a uma camada pode depender de objetos das camadas inferiores, mas nunca de objetos das camadas superiores.

Mas não sei o que pensar sobre objetos que dependem de outros objetos da mesma camada.

Como exemplo, vamos assumir um aplicativo com três camadas e vários objetos como o da imagem. Obviamente, dependências de cima para baixo (setas verdes) estão ok, de baixo para cima (seta vermelha) não, mas que tal uma dependência dentro da mesma camada (seta amarela)?

insira a descrição da imagem aqui

Excluindo a dependência circular, estou curioso sobre qualquer outro problema que possa surgir e o quanto a arquitetura em camadas está sendo violada neste caso.

bracco23
fonte
Se não têm ciclos, em uma camada, onde tem as ligações horysontal existe dividindo-se em sub-camadas que tem apenas as dependências entre as camadas
max630

Respostas:

10

Sim, objetos em uma camada podem ter dependências diretas entre si, às vezes até cíclicas - é isso que faz a diferença principal entre as dependências permitidas entre objetos em diferentes camadas, nas quais não são permitidas dependências diretas ou apenas uma dependência estrita direção.

No entanto, isso não significa que eles devam ter essas dependências de maneira arbitrária. Na verdade, depende do que suas camadas representam, do tamanho do sistema e da responsabilidade das partes. Observe que "arquitetura em camadas" é um termo vago, há uma enorme variação do que isso realmente significa em diferentes tipos de sistemas.

Por exemplo, digamos que você tenha um "sistema em camadas horizontalmente", com uma camada de banco de dados, uma camada de negócios e uma camada de interface do usuário. Vamos dizer que a camada da interface do usuário contém pelo menos várias dezenas de classes de diálogo diferentes.

Pode-se escolher um design em que nenhuma das classes de diálogo dependa diretamente de outra classe de diálogo. Pode-se escolher um design onde existam "caixas de diálogo principais" e "sub-caixas de diálogo" e existem apenas dependências diretas das caixas de diálogo "principais" para "sub-caixas". Ou pode-se preferir um design em que qualquer classe de interface do usuário existente possa usar / reutilizar qualquer outra classe de interface da mesma camada.

Essas são todas as opções de design possíveis, talvez mais ou menos sensíveis, dependendo do tipo de sistema que você está construindo, mas nenhuma delas invalida as "camadas" do seu sistema.

Doc Brown
fonte
Continuando no exemplo da interface do usuário, quais são os prós e os contras de ter as dependências internas? Percebo que isso facilita a reutilização e a introdução de dependências cíclicas, o que pode ser um problema, dependendo do método DI utilizado. Algo mais?
precisa saber é
@ bracco23: os prós e os contras de ter dependências "internas" são os mesmos que os prós e contras de ter dependências arbitrárias. "Contras": eles tornam os componentes mais difíceis de reutilizar e mais difíceis de testar, especialmente isolados de outros componentes. Os profissionais são: dependências explícitas facilitam o uso, a compreensão, o gerenciamento e o teste de coesão.
Doc Brown,
14

Estou confortável em dizer que um objeto pertencente a uma camada pode depender de objetos de camadas inferiores

Para ser sincero, não acho que você deva se sentir confortável com isso. Ao lidar com qualquer coisa, exceto um sistema trivial, pretendo garantir que todas as camadas dependam apenas de abstrações de outras camadas; ambos inferiores e superiores.

Então, por exemplo, Obj 1não deve depender Obj 3. Deve ter uma dependência de, por exemplo, IObj 3e deve ser informado com qual implementação dessa abstração ela deve trabalhar em tempo de execução. O que está sendo dito não deve estar relacionado a nenhum dos níveis, pois é o trabalho de mapear essas dependências. Pode ser um contêiner de IoC, código personalizado chamado, por exemplo, mainque utiliza DI puro. Ou a qualquer momento, pode até ser um localizador de serviço. Independentemente, as dependências não existem entre as camadas até que essa coisa forneça o mapeamento.

Mas não sei o que pensar sobre objetos que dependem de outros objetos da mesma camada.

Eu diria que esta é a única vez que você deve ter dependências diretas. Faz parte do funcionamento interno dessa camada e pode ser alterado sem afetar outras camadas. Portanto, não é um acoplamento prejudicial.

David Arno
fonte
Obrigado pela resposta. Sim, a camada não deve expor objetos, mas interfaces, eu sei disso, mas deixei de fora por uma questão de simplicidade.
bracco23
4

Vamos olhar para isso praticamente

insira a descrição da imagem aqui

Obj 3agora sabe que Obj 4existe. E daí? Por que nos importamos?

MERGULHO diz

"Módulos de alto nível não devem depender de módulos de baixo nível. Ambos devem depender de abstrações."

OK, mas não são todas as abstrações de objetos?

DIP também diz

"Abstrações não devem depender de detalhes. Detalhes devem depender de abstrações."

OK, mas se meu objeto estiver encapsulado corretamente, isso não oculta nenhum detalhe?

Algumas pessoas gostam de insistir cegamente que todo objeto precisa de uma interface de palavra-chave. Eu não sou um deles. Eu gosto de insistir cegamente que, se você não vai usá-los agora, precisa de um plano para lidar com a necessidade de algo como eles mais tarde.

Se o seu código for totalmente refatorável a cada versão, você poderá extrair as interfaces posteriormente, se precisar delas. Se você publicou um código que não deseja recompilar e deseja estar falando através de uma interface, precisará de um plano.

Obj 3sabe que Obj 4existe. Mas Obj 3sabe se Obj 4é concreto?

É por isso que é tão bom NÃO se espalhar por newtoda parte. Se Obj 3não souber se Obj 4é concreto, provavelmente porque não o criou, então se você Obj 4entrar mais tarde e se transformar em uma classe abstrataObj 3 , não se importaria.

Se você pode fazer isso, Obj 4foi totalmente abstrato o tempo todo. A única coisa que faz uma interface entre eles desde o início é a garantia de que alguém não adicionará acidentalmente código que denuncie que Obj 4é concreto agora. Construtores protegidos podem atenuar esse risco, mas isso leva a outra pergunta:

Os Obj 3 e 4 estão no mesmo pacote?

Os objetos geralmente são agrupados de alguma forma (pacote, espaço para nome, etc). Quando agrupados com sabedoria, altere os impactos mais prováveis ​​dentro de um grupo do que entre grupos.

Eu gosto de agrupar por recurso. Se Obj 3eObj 4 estiver no mesmo grupo e camada, é muito improvável que você tenha publicado um e não queira refatorá-lo enquanto precisar alterar apenas o outro. Isso significa que é menos provável que esses objetos se beneficiem de uma abstração colocada entre eles antes que haja uma necessidade clara.

Se você estiver cruzando um limite de grupo, é realmente uma boa ideia permitir que os objetos de ambos os lados variem independentemente.

Deveria ser simples, mas infelizmente o Java e o C # fizeram escolhas infelizes que complicam isso.

Em C #, é tradição nomear cada interface de palavra-chave com um Iprefixo. Isso força os clientes a SABERem que estão conversando com uma interface de palavras-chave. Isso mexe com o plano de refatoração.

Em Java, é tradição usar um melhor padrão de nomenclatura: no FooImple implements Fooentanto, isso só ajuda no nível do código-fonte, pois o Java compila interfaces de palavras-chave com um binário diferente. Isso significa que, quando você refatorar Fooclientes concretos para abstratos que não precisam de um único caractere de código alterado, ainda precisará ser recompilado.

São esses BUGS nessas linguagens específicas que impedem as pessoas de adiar a abstração formal até que realmente precisem. Você não disse qual idioma está usando, mas entende que existem alguns idiomas que simplesmente não têm esses problemas.

Você não disse qual o idioma que está usando, portanto, pedimos que você analise seu idioma e situação com cuidado antes de decidir que serão interfaces de palavras-chave em todos os lugares.

O princípio YAGNI desempenha um papel fundamental aqui. Mas o mesmo acontece com "Por favor, torne difícil dar um tiro no meu pé".

candied_orange
fonte
0

Além das respostas acima, acho que pode ajudá-lo a vê-lo de diferentes pontos de vista.

Por exemplo, do ponto de vista da regra de dependência . DR é uma regra sugerida por Robert C. Martin para sua famosa arquitetura limpa .

Diz

As dependências do código-fonte devem apontar apenas para dentro, em direção a políticas de nível superior.

Por políticas de nível superior , ele quer dizer abstrações de nível superior . Componentes que vazam nos detalhes da implementação, como por exemplo, interfaces ou classes abstratas em contraste com classes concretas ou estruturas de dados.

O problema é que a regra não se restringe à dependência entre camadas. Apenas indica a dependência entre partes do código, independentemente do local ou da camada a que pertencem.

Portanto, não, não há nada inerentemente errado em ter dependências entre elementos da mesma camada. No entanto, a dependência ainda pode ser implementada para transmitir o princípio da dependência estável .

Outro ponto de vista é SRP.

A dissociação é a nossa maneira de quebrar dependências prejudiciais e transmitir algumas práticas recomendadas, como inversão de dependência (IoC). No entanto, os elementos que compartilham os motivos da mudança não fornecem motivos para a dissociação, porque os elementos com o mesmo motivo para mudar serão alterados ao mesmo tempo (muito provavelmente) e serão implantados ao mesmo tempo. Se for esse o caso entre Obj3e Obj4, mais uma vez, não há nada inerentemente errado.

Laiv
fonte