Existem tipos de OOP em que alguns ou todos os princípios do SOLID são antitéticos ao código limpo?

26

Recentemente, tive uma discussão com um amigo meu sobre OOP no desenvolvimento de videogames.

Eu estava explicando a arquitetura de um dos meus jogos que, para surpresa do meu amigo, continha muitas classes pequenas e várias camadas de abstração. Argumentei que esse foi o resultado de me concentrar em dar a tudo uma única responsabilidade e também em afrouxar o acoplamento entre os componentes.

Sua preocupação era que o grande número de classes se traduzisse em um pesadelo de manutenção. Na minha opinião, isso teria o efeito exatamente oposto. Passamos a discutir isso pelo que pareceu séculos, finalmente concordando em discordar, dizendo que talvez houvesse casos em que os princípios do SOLID e a POO adequada não se misturassem bem.

Até a entrada da Wikipedia sobre os princípios do SOLID afirma que são diretrizes que ajudam a escrever código sustentável e que fazem parte de uma estratégia geral de programação ágil e adaptável.

Então, minha pergunta é:

Existem casos no OOP em que alguns ou todos os princípios do SOLID não se prestam à limpeza de código?

Posso imaginar imediatamente que o Princípio da Substituição de Liskov poderia entrar em conflito com outro sabor de herança segura. Ou seja, se alguém idealizou outro padrão útil implementado por meio da herança, é bem possível que o LSP esteja em conflito direto com ele.

Existem outros? Talvez certos tipos de projetos ou determinadas plataformas de destino funcionem melhor com uma abordagem menos SOLID?

Editar:

Gostaria apenas de especificar que não estou perguntando como melhorar meu código;) A única razão pela qual mencionei um projeto nesta pergunta foi dar um pequeno contexto. Minha pergunta é sobre OOP e princípios de design em geral .

Se você está curioso sobre o meu projeto, veja isso .

Edição 2:

Imaginei que essa pergunta seria respondida de uma de três maneiras:

  1. Sim, existem princípios de projeto OOP que conflitam parcialmente com o SOLID
  2. Sim, existem princípios de projeto OOP que conflitam completamente com o SOLID
  3. Não, o SOLID é o joelho da abelha e a POO será sempre melhor com ela. Mas, como em tudo, não é uma panacéia. Beba com responsabilidade.

As opções 1 e 2 provavelmente teriam gerado respostas longas e interessantes. A opção 3, por outro lado, seria uma resposta curta, desinteressante, mas reconfortante.

Parece que estamos convergindo para a opção 3.

MetaFight
fonte
Como você está definindo 'código limpo'?
GrandmasterB
O código limpo, no escopo desta pergunta, é a estrutura do código de maneira a atender aos objetivos do OOP e a um conjunto de princípios de design escolhidos. Portanto, estou familiarizado com o código limpo em termos dos objetivos estabelecidos pelo OOP + SOLID. Gostaria de saber se existe outro conjunto de princípios de design que funcionam com OOP, mas são total ou parcialmente incompatíveis com o SOLID.
MetaFight 04/04
1
Estou muito mais preocupado com o desempenho desse seu jogo do que com qualquer outra coisa. A menos que estejamos falando de jogos baseados em texto.
eufórico
1
Este jogo é essencialmente um experimento. Estou testando diferentes abordagens de desenvolvimento e também tentando verificar se o código da empresa pode funcionar ao escrever um jogo. Eu estou esperando que o desempenho se torne um problema, mas ainda não o fez. Se / quando acontecer, a próxima coisa que experimentarei é como adaptar meu código ao código de desempenho. Tanta aprendizagem!
MetaFight 04/04
1
Correndo o risco de algumas votações negativas, gostaria de voltar à pergunta "por que": OOP no desenvolvimento de jogos . Devido à natureza do desenvolvimento de jogos, o OOP "tradicional" parece estar perdendo moda em favor de vários tipos de sistemas de entidade / componente, uma variante interessante aqui, por exemplo. Isso me faz pensar que a pergunta não deve ser "SOLID + OOP", mas "OOP + Game Development". Quando você olha para um gráfico de interação de objetos para o desenvolvedor de jogos. vs. dizer aplicação em camadas N, as diferenças podem significar que um novo paradigma é bom.
perfil completo de J Trana

Respostas:

20

Existem casos no OOP em que alguns ou todos os princípios do SOLID não se prestam à limpeza de código?

Em geral, não. A história mostrou que todos os princípios do SOLID contribuem amplamente para aumentar a dissociação, o que, por sua vez, demonstrou aumentar a flexibilidade no código e, portanto, sua capacidade de acomodar as mudanças, além de facilitar o raciocínio, o teste e a reutilização. em resumo, torne seu código mais limpo.

Agora, pode haver casos em que os princípios do SOLID colidem com DRY (não se repita), KISS (mantenha a simplicidade estúpida) ou outros princípios de bom design de OO. E, é claro, eles podem colidir com a realidade dos requisitos, as limitações humanas, as limitações de nossas linguagens de programação, outros obstáculos.

Em resumo, os princípios do SOLID sempre se prestam a código limpo, mas em alguns cenários se prestam menos do que alternativas conflitantes. Eles são sempre bons, mas às vezes outras coisas são mais boas.

Telastyn
fonte
14
Eu gosto, mas às vezes outras coisas são mais boas . Tem uma sensação de "Animal Farm". ;)
FrustratedWithFormsDesigner
12
+1 Quando acabei de analisar os princípios do SOLID, vejo 5 "bons de ter". Mas nenhum deles supera DRY, KISS e "deixa suas intenções claras". Use SOLID, mas mostre cautela e ceticismo.
precisa saber é o seguinte
Concordo. Eu acho que o (s) princípio (s) correto (s) a seguir depende claramente da situação, mas, além dos requisitos de desempenho difíceis, que sempre se classificam acima de tudo (isso importa principalmente no desenvolvimento de jogos), costumo pensar que DRY e KISS são geralmente mais importantes do que o SOLID. Obviamente, quanto mais limpo você tornar o código, melhor; portanto, se puder seguir todos os princípios sem conflitos, melhor.
Ben Lee
E a segregação da integração versus o design eficiente dos agregados? Se a interface básica de "sequência" [por exemplo, IEnumerable] incluir métodos como Counte asImmutable, e propriedades como getAbilities[cujo retorno indicará se coisas como Countserão "eficientes"], seria possível ter um método estático que pega várias seqüências e as agrega para que elas se comportará como uma única sequência mais longa. Se essas habilidades estiverem presentes no tipo de sequência básica, mesmo que elas estejam
vinculadas
... e implementá-los de forma eficiente quando usados ​​com classes base que podem fazer isso. Se alguém agrega uma lista de dez milhões de itens que conhece sua contagem e uma lista de cinco itens que só pode recuperar itens seqüencialmente, uma solicitação para a entrada 10.000,003rd deve ler a contagem da primeira lista e, em seguida, ler três itens da segunda . As interfaces de pia de cozinha podem violar o ISP, mas podem melhorar bastante o desempenho de alguns cenários de composição / agregação.
Supercat
13

Acho que tenho uma perspectiva incomum, pois trabalhei em finanças e jogos.

Muitos dos programadores que conheci nos jogos eram péssimos na Engenharia de Software - mas eles não precisavam de práticas como o SOLID. Tradicionalmente, eles colocam o jogo em uma caixa - e terminam.

Em finanças, descobri que os desenvolvedores podem ser muito desleixados e indisciplinados porque não precisam do desempenho que você faz nos jogos.

Ambas as instruções acima são naturalmente generalizações excessivas, mas, na verdade, em ambos os casos, o código limpo é essencial. Para uma otimização clara e fácil de entender, o código é essencial. Por uma questão de manutenção, você quer a mesma coisa.

Isso não quer dizer que o SOLID não esteja isento de críticas. Eles são chamados de princípios e, no entanto, devem ser seguidos como diretrizes? Então, quando exatamente devo segui-los? Quando devo quebrar as regras?

Você provavelmente pode olhar para dois trechos de código e dizer qual deles atende melhor ao SOLID. Porém, isoladamente, não há uma maneira objetiva de transformar o código em SOLID. É definitivamente a interpretação do desenvolvedor.

É um não sequitur dizer que "um grande número de classes pode levar a um pesadelo de manutenção". No entanto, se o SRP não for interpretado corretamente, você poderá facilmente acabar com esse problema.

Eu vi código com muitas camadas de praticamente nenhuma responsabilidade. Classes que não fazem nada além do estado de passagem de uma classe para outra. O problema clássico de muitas camadas de indireção.

O SRP, se abusado, pode resultar em falta de coesão no seu código. Você pode dizer isso ao tentar adicionar um recurso. Se você sempre precisar alterar vários locais ao mesmo tempo, seu código não terá coesão.

Aberto-fechado não deixa de ter seus críticos. Por exemplo, veja a revisão de Jon Skeet do princípio Aberto Fechado . Não vou reproduzir seus argumentos aqui.

Seu argumento sobre o LSP parece bastante hipotético e eu realmente não entendo o que você quer dizer com outra forma de herança segura?

O ISP parece duplicar um pouco o SRP. Certamente eles devem ser interpretados da mesma maneira que você nunca pode prever o que todos os clientes da sua interface farão. A interface coesa de hoje é o violador do ISP de amanhã. A única conclusão ilógica é não ter mais de um membro por interface.

DIP pode levar a código inutilizável. Eu já vi classes com um grande número de parâmetros em nome de DIP. Essas classes normalmente "atualizavam" suas partes compostas, mas, como nome da capacidade de teste, nunca devemos usar a nova palavra-chave novamente.

O SOLID por si só não é suficiente para você escrever um código limpo. Eu vi o livro de Robert C. Martin, " Código Limpo: Um Manual de Artesanato em Software Ágil ". O maior problema é que é fácil ler este livro e interpretá-lo como regras. Se você fizer isso, está perdendo o objetivo. Ele contém uma grande lista de odores, heurísticas e princípios - muitos mais do que apenas os cinco do SOLID. Todos esses "princípios" não são realmente princípios, mas diretrizes. O tio Bob os descreve como "cubículos" para conceitos. Eles são um ato de equilíbrio; seguir cegamente qualquer diretriz causará problemas.

Dave Hillier
fonte
1
Se você tiver um grande número de parâmetros de construtor, isso sugere que você violou o SRP. No entanto, um contêiner de DI remove a dor associada a um grande número de parâmetros de construtores de qualquer maneira, por isso discordo de suas declarações sobre DIP.
Stephen
Todos esses "princípios" não são realmente princípios, mas diretrizes. Eles são um ato de equilíbrio; como eu disse no meu post após o SRP também, o extremo em si pode ser problemático.
Dave Hillier
Boa resposta, +1. Uma coisa que gostaria de acrescentar: li a resenha de Skeet mais uma crítica da definição padrão de livros didáticos do OCP, não das idéias centrais por trás do OCP. A meu ver, a ideia de variação protegida é o que o OCP deveria ter sido desde o início.
Doc Brown
5

Aqui está a minha opinião:

Embora os princípios do SOLID tenham como objetivo uma base de código não redundante e flexível, isso pode ser uma troca de legibilidade e manutenção se houver muitas classes e camadas.

É especialmente sobre o número de abstrações . Um grande número de classes pode ser bom se elas forem baseadas em poucas abstrações. Como não sabemos o tamanho e as especificidades do seu projeto, é difícil dizer, mas é um pouco preocupante que você mencione várias camadas, além de um grande número de classes.

Eu acho que os princípios do SOLID não discordam do OOP, mas ainda é possível escrever um código não limpo aderindo a eles.

henginy
fonte
3
+1 Por definição quase, um sistema altamente flexível deve ser menos sustentável do que um que é menos flexível. A flexibilidade tem um custo devido à complexidade adicional que ela traz.
Andy
@ Andy não necessariamente. no meu trabalho atual, muitas das piores partes do código são confusas porque são criadas juntas por "fazer algo simples, apenas junte-o e faça-o funcionar para que não fique tão complexo". Como resultado, muitas partes do sistema são 100% rígidas. Você não pode modificá-los sem reescrever grandes partes deles. É realmente difícil de manter. A manutenção vem da alta coesão e baixo acoplamento, não da flexibilidade do sistema.
Sara
3

Nos meus primeiros dias de desenvolvimento, comecei a escrever um Roguelike em C ++. Como eu queria aplicar a boa metodologia orientada a objetos que aprendi com minha formação, vi imediatamente os itens do jogo como objetos C ++. Potion, Swords, Food, Axe, JavelinsEtc. todos derivados de um núcleo básico Itemdo código, nome, ícone, peso, esse tipo de coisa manipulação.

Codifiquei a lógica do saco e enfrentei um problema. Posso colocar Itemsna bolsa, mas, se eu escolher uma, como sei se é uma Potionou uma Sword? Procurei na Internet como diminuir itens. Eu hackeei as opções do compilador para habilitar as informações de tempo de execução para saber quais são meus Itemtipos verdadeiros abstratos e comecei a mudar a lógica dos tipos para saber quais operações eu permitiria (por exemplo, evite beber Swordse colocar de Potionspé)

Essencialmente, transformou o código em uma grande bola de lama meio copipada. À medida que o projeto prosseguia, comecei a entender qual era a falha no design. O que aconteceu é que tentei usar classes para representar valores. Minha hierarquia de classes não era uma abstração significativa. O que eu deveria ter feito é fazer Itemimplementar um conjunto de funções, tais como Equip (), Apply ()e Throw (), e fazer com que cada comportam com base em valores. Usando enums para representar os slots de equipamento. Usando enums para representar diferentes tipos de armas. Usando mais valores e menos classificação, porque minha subclassificação não tinha outro propósito senão preencher esses valores finais.

Como você está enfrentando a multiplicação de objetos em uma camada de abstração, acho que meu insight pode ser valioso aqui. Se você parece ter muitos tipos de objetos, pode ser que você esteja confundindo o que deve ser tipos com o que devem ser valores.

Arthur Havlicek
fonte
2
O problema era confundir tipos com classes de valores (nota: não estou usando a palavra classe para se referir à noção de classe OOP / C ++.) Há mais de uma maneira de representar um ponto em um plano cartesiano (coordenadas retangulares, polares). coordenadas), mas todos fazem parte do mesmo tipo. No entanto, as principais linguagens de POO não suportam a classificação de valores naturalmente. A coisa mais próxima disso é a união do C / C ++, mas você precisa adicionar um campo extra apenas para saber o que diabos você coloca nele.
Doval
4
Sua experiência pode ser usada como anti-exemplo. Ao não usar o SOLID nem qualquer tipo de metodologia de modelagem, você criou um código não-sustentável e não-extensível.
eufórico
1
@Euphoric eu tentei muito. Eu tinha metodologia orientada a objetos. Há uma diferença entre ter uma metodologia e sucesso implementá-lo, embora :)
Arthur Havlicek
2
Como isso é um exemplo de princípios do SOLID que são contrários ao código limpo no OOP? Parece mais um exemplo de design incorreto - isso é ortogonal ao OOP!
Andres F.
1
Sua classe ITEM de base deveria ter vários métodos booleanos "canXXXX", todos com o padrão FALSE. Você pode então definir "canThrow ()" para retornar true em sua classe Javelin, e canEat () para retornar true para sua classe Postion.
James Anderson
3

Sua preocupação era que o grande número de classes se traduzisse em um pesadelo de manutenção. Na minha opinião, isso teria o efeito exatamente oposto.

Estou absolutamente do lado de seu amigo, mas pode ser uma questão de nossos domínios e os tipos de problemas e projetos que enfrentamos e, especialmente, que tipos de coisas provavelmente exigirão mudanças no futuro. Problemas diferentes, soluções diferentes. Não acredito no certo ou no errado, apenas nos programadores que tentam encontrar a melhor maneira de resolver seus problemas de design específicos. Eu trabalho em efeitos visuais, o que não é muito diferente dos mecanismos de jogo.

Mas o problema para mim com o qual lutei no que poderia ser chamado de arquitetura um pouco mais compatível com o SOLID (era baseada em COM), pode ser resumido a "muitas classes" ou "muitas funções", como seu amigo pode descrever. Eu diria especificamente: "muitas interações, muitos lugares que poderiam se comportar mal, muitos lugares que poderiam causar efeitos colaterais, muitos lugares que talvez precisassem mudar e muitos lugares que talvez não fizessem o que pensamos que eles fazem . "

Tínhamos um punhado de interfaces abstratas (e puras) implementadas por uma grande quantidade de subtipos, assim: (fizemos este diagrama no contexto de falar sobre os benefícios do ECS, desconsidere o comentário no canto inferior esquerdo):

insira a descrição da imagem aqui

Onde uma interface de movimento ou uma interface de nó de cena pode ser implementada por centenas de subtipos: luzes, câmeras, malhas, solucionadores de física, sombreadores, texturas, ossos, formas primitivas, curvas, etc. etc. (e geralmente havia vários tipos de cada ) E o problema final era realmente que esses projetos não eram tão estáveis. Tivemos mudanças nos requisitos e, às vezes, as próprias interfaces precisavam mudar, e quando você deseja alterar uma interface abstrata implementada por 200 subtipos, é uma mudança extremamente cara. Começamos a mitigar isso usando classes básicas abstratas entre as quais reduzimos os custos de tais alterações de design, mas elas ainda eram caras.

Então, alternativamente, comecei a explorar a arquitetura do sistema de componente de entidade usada com bastante frequência na indústria de jogos. Isso mudou tudo para ficar assim:

insira a descrição da imagem aqui

E uau! Essa foi uma diferença em termos de manutenção. As dependências não fluíram mais para abstrações , mas para dados (componentes). E no meu caso, pelo menos, os dados eram muito mais estáveis ​​e mais fáceis de acertar em termos de design, apesar das mudanças nos requisitos (embora o que possamos fazer com os mesmos dados esteja constantemente mudando com os requisitos).

Também porque as entidades em um ECS usam composição em vez de herança, elas realmente não precisam conter funcionalidade. Eles são apenas o "recipiente de componentes" analógico. Com isso, os 200 subtipos analógicos que implementaram uma interface de movimento se transformam em 200 instâncias de entidade (não tipos separados com código separado) que simplesmente armazenam um componente de movimento (que não passa de dados associados ao movimento). A PointLightnão é mais uma classe / subtipo separado. Não é uma aula. É uma instância de uma entidade que apenas combina alguns componentes (dados) relacionados a onde está no espaço (movimento) e as propriedades específicas das luzes pontuais. A única funcionalidade associada a eles está dentro dos sistemas, como oRenderSystem, que procura componentes leves na cena para determinar como renderizar a cena.

Com a alteração dos requisitos sob a abordagem do ECS, muitas vezes havia apenas a necessidade de alterar um ou dois sistemas operando com esses dados ou apenas introduzir um novo sistema ao lado ou introduzir um novo componente, se novos dados fossem necessários.

Portanto, para o meu domínio, pelo menos, e tenho quase certeza de que não é para todos, isso facilitou muito as coisas, porque as dependências estavam fluindo em direção à estabilidade (coisas que não precisavam mudar com frequência). Esse não era o caso na arquitetura COM quando as dependências estavam fluindo uniformemente em direção às abstrações. No meu caso, é muito mais fácil descobrir quais dados são necessários para o movimento antecipadamente, em vez de todas as coisas possíveis que você poderia fazer com eles, o que geralmente muda um pouco ao longo dos meses ou anos à medida que novos requisitos chegam.

insira a descrição da imagem aqui

Existem casos no OOP em que alguns ou todos os princípios do SOLID não se prestam à limpeza de código?

Bem, código limpo, não posso dizer, já que algumas pessoas equiparam o código limpo ao SOLID, mas, definitivamente, existem alguns casos em que separar dados da funcionalidade como o ECS e redirecionar dependências das abstrações para os dados podem definitivamente facilitar muito as coisas. mudar, por razões óbvias de acoplamento, se os dados forem muito mais estáveis ​​que as abstrações. É claro que dependências de dados podem dificultar a manutenção de invariantes, mas o ECS tende a atenuar isso ao mínimo com a organização do sistema, o que minimiza o número de sistemas que acessam qualquer tipo de componente.

Não é necessariamente que as dependências fluam para abstrações, como sugere o DIP; as dependências devem fluir para coisas que dificilmente precisarão de mudanças futuras. Isso pode ou não ser abstrações em todos os casos (certamente não estava no meu).

  1. Sim, existem princípios de projeto OOP que conflitam parcialmente com o SOLID
  2. Sim, existem princípios de projeto OOP que conflitam completamente com o SOLID.

Não tenho certeza se o ECS é realmente um sabor de POO. Algumas pessoas o definem dessa maneira, mas eu o vejo como muito diferente inerentemente às características do acoplamento e à separação de dados (componentes) da funcionalidade (sistemas) e à falta de encapsulamento de dados. Se for considerado uma forma de POO, acho que está em conflito com o SOLID (pelo menos as idéias mais estritas de SRP, aberto / fechado, substituição de liskov e DIP). Mas espero que este seja um exemplo razoável de um caso e domínio em que os aspectos mais fundamentais do SOLID, pelo menos como as pessoas geralmente os interpretem em um contexto de POO mais reconhecível, possam não ser tão aplicáveis.

Teeny Classes

Eu estava explicando a arquitetura de um dos meus jogos que, para surpresa do meu amigo, continha muitas classes pequenas e várias camadas de abstração. Argumentei que esse foi o resultado de me concentrar em dar a tudo uma única responsabilidade e também em afrouxar o acoplamento entre os componentes.

O ECS desafiou e mudou muito meus pontos de vista. Como você, eu pensava que a própria idéia de manutenção é ter a implementação mais simples para as coisas possíveis, o que implica muitas coisas e, além disso, muitas coisas interdependentes (mesmo que as interdependências sejam entre abstrações). Faz mais sentido se você estiver ampliando apenas uma classe ou função para querer ver a implementação mais direta e simples e, se não encontrarmos uma, refatorar e talvez até decompor ainda mais. Mas pode ser fácil perder o que está acontecendo com o mundo exterior como resultado, porque sempre que você divide algo relativamente complexo em 2 ou mais coisas, essas 2 ou mais coisas devem inevitavelmente interagir * (veja abaixo) entre si em alguns maneira, ou algo de fora tem que interagir com todos eles.

Hoje em dia, acho que há um ato de equilíbrio entre a simplicidade de algo e quantas coisas existem e quanta interação é necessária. Os sistemas em um ECS tendem a ser bastante pesados ​​com implementações não triviais para operar nos dados, como PhysicsSystemou RenderSystemou GuiLayoutSystem. No entanto, o fato de um produto complexo precisar de poucos deles tende a facilitar a recuperação e o raciocínio sobre o comportamento geral de toda a base de código. Há algo nele que pode sugerir que pode não ser uma má idéia apoiar-se em menos classes mais volumosas (ainda executando uma responsabilidade indiscutivelmente singular), se isso significa menos classes para manter e raciocinar e menos interações ao longo o sistema.

Interações

Digo "interações" em vez de "acoplamento" (embora reduzir interações implique em reduzir as duas), pois é possível usar abstrações para desacoplar dois objetos concretos, mas eles ainda conversam entre si. Eles ainda podem causar efeitos colaterais no processo dessa comunicação indireta. E, freqüentemente, acho que minha capacidade de raciocinar sobre a correção de um sistema está mais relacionada a essas "interações" do que a "acoplamento". Minimizar as interações tende a facilitar muito as coisas para eu raciocinar sobre tudo, do ponto de vista de um pássaro. Isso significa que as coisas não se falam e, nesse sentido, a ECS também tende a realmente minimizar as "interações", e não apenas o acoplamento, ao mínimo dos mínimos (pelo menos eu não

Dito isto, isso pode ser pelo menos parcialmente eu e minhas fraquezas pessoais. Eu encontrei o maior impedimento para eu criar sistemas de enorme escala, e ainda raciocinar com confiança sobre eles, navegar por eles e sentir que posso fazer as possíveis mudanças desejadas em qualquer lugar de uma maneira previsível, seja o gerenciamento de estado e recursos, juntamente com efeitos colaterais. É o maior obstáculo que começa a surgir à medida que passo de dezenas de milhares de LOC a centenas de milhares de LOC a milhões de LOC, mesmo para códigos criados por mim mesmo. Se algo vai me atrasar para um rastreamento acima de tudo, é nesse sentido que não consigo mais entender o que está acontecendo em termos de estado do aplicativo, dados e efeitos colaterais. Isto' não é o tempo robótico necessário para fazer uma mudança que me atrapalha tanto quanto a incapacidade de entender todos os impactos da mudança se o sistema crescer além da capacidade da minha mente de raciocinar sobre ela. E reduzir as interações tem sido, para mim, a maneira mais eficaz de permitir que o produto cresça muito mais, com muito mais recursos, sem que eu fique pessoalmente impressionado com essas coisas, pois reduzir as interações ao mínimo também reduz o número de locais que podem possivelmente altere o estado do aplicativo e cause efeitos colaterais substancialmente.

Pode virar algo assim (onde tudo no diagrama tem funcionalidade e, obviamente, um cenário do mundo real teria muitas, muitas vezes o número de objetos, e este é um diagrama de "interação", não um acoplamento, como um acoplamento um teria abstrações no meio):

insira a descrição da imagem aqui

... para isso em que apenas os sistemas têm funcionalidade (os componentes azuis agora são apenas dados e agora este é um diagrama de acoplamento):

insira a descrição da imagem aqui

E há pensamentos emergentes sobre tudo isso e talvez uma maneira de enquadrar alguns desses benefícios em um contexto de POO mais compatível e mais compatível com o SOLID, mas ainda não encontrei os desenhos e as palavras, e acho difícil desde a terminologia que eu estava acostumado a abordar todos relacionados diretamente ao POO. Eu continuo tentando descobrir isso lendo as respostas das pessoas aqui e também tentando o meu melhor para formular as minhas, mas há algumas coisas muito interessantes sobre a natureza de um ECS que eu não consegui identificar perfeitamente. pode ser mais amplamente aplicável, mesmo para arquiteturas que não o usam. Também espero que essa resposta não seja uma promoção da ECS! Eu acho isso muito interessante, já que projetar um ECS realmente mudou meus pensamentos drasticamente,


fonte
Eu gosto da distinção entre interações e acoplamentos. Embora seu primeiro diagrama de interação seja ofuscado. É um gráfico planar, portanto não há necessidade de cruzar as setas.
precisa saber é o seguinte
2

Os princípios do SOLID são bons, mas o KISS e o YAGNI têm prioridade em caso de conflito. O objetivo do SOLID é gerenciar a complexidade, mas, ao aplicar o próprio SOLID, o código fica mais complexo, mas o objetivo é derrotado. Por exemplo, se você tiver um programa pequeno com apenas algumas classes, a aplicação de DI, segregação de interface etc. poderá muito bem aumentar a complexidade geral do programa.

O princípio aberto / fechado é especialmente controverso. Basicamente, diz que você deve adicionar recursos a uma classe criando uma subclasse ou uma implementação separada da mesma interface, mas deixe a classe original intocada. Se você estiver mantendo uma biblioteca ou serviço usado por clientes não coordenados, isso faz sentido, pois você não deseja interromper o comportamento no qual o cliente existente pode confiar. No entanto, se você estiver modificando uma classe que é usada apenas em seu próprio aplicativo, muitas vezes seria muito mais simples e mais limpo simplesmente modificar a classe.

JacquesB
fonte
Sua discussão sobre o OCP ignora a possibilidade de estender o comportamento de uma classe via composição (por exemplo, usando um objeto de estratégia para permitir que um algoritmo usado pela classe seja alterado), que geralmente é considerado uma maneira melhor de cumprir o principal do que a subclasse .
Jules
1
Se o KISS e o YAGNI sempre tiveram prioridade, você ainda deve codificar 1 e 0. Montadores são apenas outra coisa que pode dar errado. KISS e YAGNI não apontam dois dos muitos custos do trabalho de design. Esse custo deve sempre ser equilibrado com os benefícios de qualquer trabalho de design. O SOLID possui benefícios que, em alguns casos, compensam os custos. Não há uma maneira simples de saber quando você cruzou a linha.
candied_orange
@CandiedOrange: Não concordo que o código da máquina seja mais simples que o código em um idioma de alto nível. O código da máquina acaba contendo muita complexidade acidental devido à falta de abstração, portanto, definitivamente não é o KISS decidir escrever uma aplicação típica no código da máquina.
precisa saber é o seguinte
@Jules: Sim, a composição é preferível, mas ainda exige que você projete a classe original de forma que a composição possa ser usada para personalizá-la; portanto, você deve fazer suposições sobre quais aspectos precisam ser personalizados no futuro e quais aspectos não são personalizáveis. Em alguns casos, isso é necessário (por exemplo, você está escrevendo uma biblioteca para distribuição), mas tem um custo, portanto, seguir o SOLID nem sempre é o ideal.
precisa saber é o seguinte
1
@JacquesB - true. E a OCP é, em sua essência, oposta à YAGNI; no entanto, você deve projetar pontos de extensão para permitir melhorias posteriores. Encontrar um ponto de equilíbrio adequado entre os dois ideais é ... complicado.
Jules
1

Em minha experiência:-

Classes grandes são exponencialmente mais difíceis de manter à medida que aumentam.

Classes pequenas são mais fáceis de manter e a dificuldade de manter uma coleção de classes pequenas aumenta aritmeticamente para o número de classes.

Às vezes, aulas grandes são inevitáveis; um grande problema às vezes precisa de uma aula grande, mas são muito mais difíceis de ler e entender. Para classes menores bem nomeadas, apenas o nome pode ser suficiente para a compreensão. Você nem precisa olhar o código, a menos que exista um problema muito específico com a classe.

James Anderson
fonte
0

Sempre acho difícil traçar a linha entre o 'S' do sólido e a (pode ser antiquada) necessidade de deixar um objeto ter todo o conhecimento de si mesmo.

Há 15 anos, trabalhei com desenvolvedores Deplhi que tiveram seu santo graal para ter aulas que continham tudo. Portanto, para uma hipoteca, você pode adicionar seguros e pagamentos, renda, empréstimos e informações fiscais, além de calcular impostos a pagar, impostos a deduzir e ele pode serializar-se, persistir em disco etc. etc.

E agora eu tenho o mesmo objeto dividido em várias classes menores, que têm a vantagem de poderem ser testadas em unidade muito mais fácil e melhor, mas não oferecem uma boa visão geral de todo o modelo de negócios.

E sim, eu sei que você não deseja vincular seus objetos ao banco de dados, mas pode injetar um repositório na grande classe de hipotecas para resolver isso.

Além disso, nem sempre é fácil encontrar bons nomes para as classes que fazem apenas uma pequena coisa.

Então, eu não tenho certeza se o 'S' sempre é uma boa ideia.

Michel
fonte