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)?
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.
fonte
Respostas:
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.
fonte
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 1
não deve dependerObj 3
. Deve ter uma dependência de, por exemplo,IObj 3
e 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,main
que 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.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.
fonte
Vamos olhar para isso praticamente
Obj 3
agora sabe queObj 4
existe. E daí? Por que nos importamos?OK, mas não são todas as abstrações de objetos?
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 3
sabe queObj 4
existe. MasObj 3
sabe seObj 4
é concreto?É por isso que é tão bom NÃO se espalhar por
new
toda parte. SeObj 3
não souber seObj 4
é concreto, provavelmente porque não o criou, então se vocêObj 4
entrar mais tarde e se transformar em uma classe abstrataObj 3
, não se importaria.Se você pode fazer isso,
Obj 4
foi 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 queObj 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 3
eObj 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
I
prefixo. 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 Foo
entanto, 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ê refatorarFoo
clientes 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é".
fonte
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
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
Obj3
eObj4
, mais uma vez, não há nada inerentemente errado.fonte