Para mim, é um problema de acoplamento e está relacionado à granularidade do design. Até a forma mais vaga de acoplamento introduz dependências de uma coisa para outra. Se isso for feito para centenas a milhares de objetos, mesmo que sejam todos relativamente simples, adira ao SRP e mesmo que todas as dependências fluam para abstrações estáveis, isso gera uma base de código que é muito difícil de raciocinar como um todo inter-relacionado.
Existem coisas práticas que ajudam a avaliar a complexidade de uma base de código, que não é discutida com frequência no SE teórico, como a profundidade da pilha de chamadas que você pode obter antes de chegar ao fim e a profundidade que precisa percorrer antes que possa, com muita confiança, entenda todos os possíveis efeitos colaterais que podem ocorrer nesse nível da pilha de chamadas, inclusive no caso de uma exceção.
E descobri, apenas na minha experiência, que sistemas mais planos com pilhas de chamadas mais rasas tendem a ser muito mais fáceis de raciocinar. Um exemplo extremo seria um sistema de componente de entidade em que os componentes são apenas dados brutos. Somente os sistemas têm funcionalidade e, no processo de implementação e uso de um ECS, achei o sistema mais fácil de todos os tempos pensar sobre quando bases de código complexas que abrangem centenas de milhares de linhas de código se resumem basicamente a algumas dezenas de sistemas que contém toda a funcionalidade.
Coisas demais fornecem funcionalidade
A alternativa anterior, quando trabalhei em bases de código anteriores, era um sistema com centenas a milhares de objetos, na maioria pequenos, em oposição a algumas dúzias de sistemas volumosos, com alguns objetos usados apenas para passar mensagens de um objeto para outro ( Message
objeto, por exemplo, que tinha seu própria interface pública). Isso é basicamente o que você obtém analogicamente quando reverte o ECS para um ponto em que os componentes têm funcionalidade e cada combinação única de componentes em uma entidade produz seu próprio tipo de objeto. E isso tenderá a gerar funções menores e mais simples herdadas e fornecidas por infinitas combinações de objetos que modelam pequenas idéias ( Particle
objeto vs.Physics System
, por exemplo). No entanto, ele também tende a produzir um gráfico complexo de interdependências, o que dificulta o raciocínio sobre o que acontece no nível amplo, simplesmente porque existem muitas coisas na base de código que podem realmente fazer algo e, portanto, podem fazer algo errado - - tipos que não são "dados", mas tipos "objetos" com funcionalidade associada. Tipos que servem como dados puros sem funcionalidade associada não podem dar errado, pois não podem fazer nada por conta própria.
As interfaces puras não ajudam muito esse problema de compreensibilidade porque, mesmo que isso torne as "dependências em tempo de compilação" menos complicadas e ofereça mais espaço para mudanças e expansão, não torna as "dependências de tempo de execução" e as interações menos complicadas. O objeto cliente ainda acaba invocando funções em um objeto de conta concreto, mesmo que estejam sendo chamados IAccount
. O polimorfismo e as interfaces abstratas têm seus usos, mas eles não separam as coisas da maneira que realmente ajuda você a raciocinar sobre todos os efeitos colaterais que poderiam ocorrer a qualquer momento. Para atingir esse tipo de dissociação efetiva, você precisa de uma base de código que tenha muito menos itens que contenham funcionalidade.
Mais dados, menos funcionalidade
Portanto, achei a abordagem do ECS, mesmo que você não a aplique completamente, para ser extremamente útil, pois transforma o que seriam centenas de objetos em apenas dados brutos com sistemas volumosos, mais grosseiramente projetados, que fornecem todo o funcionalidade. Maximiza o número de tipos de "dados" e minimiza o número de tipos de "objetos" e, portanto, minimiza absolutamente o número de lugares no sistema que podem realmente dar errado. O resultado final é um sistema muito "plano", sem um gráfico complexo de dependências, apenas sistemas para componentes, nunca vice-versa e nunca componentes para outros componentes. São basicamente muito mais dados brutos e muito menos abstrações que têm o efeito de centralizar e nivelar a funcionalidade da base de código em áreas-chave, abstrações-chave.
30 coisas mais simples não são necessariamente mais simples de raciocinar sobre mais de uma coisa mais complexa, se essas 30 coisas mais simples estiverem inter-relacionadas enquanto a coisa complexa é independente. Portanto, minha sugestão é transferir a complexidade das interações entre objetos e mais para objetos mais volumosos que não precisam interagir com mais nada para obter dissociação em massa, para "sistemas" inteiros (não monólitos e objetos divinos, veja bem, e não classes com 200 métodos, mas algo consideravelmente mais alto que um Message
ou a Particle
apesar de ter uma interface minimalista). E favorecer tipos de dados antigos mais simples. Quanto mais você depender desses, menos acoplamento terá. Mesmo que isso contradiga algumas idéias de SE, eu descobri que isso realmente ajuda muito.