Há um problema com a descrição e interpretação dos princípios de desenvolvimento da arquitetura SOLID (autoria de Robert Martin). Muitas fontes dão sua definição e até exemplos de seu uso. Estudando-os e tentando experimentar a mim mesmo, constantemente me pego pensando que não havia explicação suficiente da magia de sua aplicação. E tentar ver as engrenagens internas, entender - e para mim isso significa lembrar - as colocou em suas "prateleiras de termos". Bem, se será útil para outra pessoa.

Prosseguimos para "manipular as prateleiras" da abordagem de design acima.
Princípio de responsabilidade única (SRP)
Um pedaço de código deve mudar apenas durante a implementação de um objetivo. Se uma seção do código implementar duas tarefas e alterações para diferentes usos, você deverá duplicar esta seção em uma instância para cada finalidade. Isso é muito importante, pois requer um afastamento do princípio geralmente aceito de eliminar a duplicação.
O objetivo deste princípio é eliminar erros implícitos introduzidos devido ao fato de existirem os invariantes a seguir para o desenvolvimento de uma seção de código, procedimento, classe, componente (daqui em diante, o termo [componente] é usado para combinar esses conceitos):
- [1] escrito corretamente [componente] é necessariamente usado e mais frequentemente várias vezes,
- [2] em cada local de uso, [componente] deverá manter um comportamento consistente, levando a um resultado repetível,
- [3] ao usar o [componente] em vários locais, o resultado deve satisfazer cada local de uso,
- se uma mudança em [componente] for necessária para um dos locais de uso e o comportamento anterior de [componente] for necessário para outro local de uso, será necessário criar uma cópia de [componente] e modificá-la (ou generalizar [componente] com parâmetros adicionais que fornecem comportamento diferente),
se existem locais de uso do [componente] que não são importantes para a tarefa atual resolvida pelo programador, é muito fácil para ele esquecer a verificação da compatibilidade com esses locais de uso das alterações feitas nesse [componente].
Portanto, todos os locais de uso devem estar localizados na zona [Responsabilidade Única] de uma única responsabilidade, ou seja, ser alterados e levados em conta imediatamente para qualquer problema resolvido pelo programador).
O princípio se aplica a uma seção de código e a um componente, biblioteca, programa, conjunto de programas usados em vários locais.
Muitas fontes dão um exemplo de classe com apenas uma "função" como ideal do SRP e a classe de "objeto divino", combinando todas as funções da aplicação, como antipadrão. Uma classe IMHO com apenas uma "função" é um requisito para a otimização prematura da arquitetura de código, solicitando a gravação de muitas classes (entidades de código) do zero, esquecendo que a ausência de mais de um local de uso permite ao programador avaliar rapidamente uma pequena quantidade localizada localmente (em uma classe) código de interação do que analisar as relações externas de entidades de código díspares responsáveis por sua "função". Um “objeto divino” para uma aplicação minúscula também não é um crime forte - permite iniciar o desenvolvimento: selecione todas as entidades necessárias e, escrevendo-as lado a lado, separe dos objetos externos da biblioteca padrão e dos módulos externos (crie uma célula viva e separe-a com uma membrana). No processo de crescimento e desenvolvimento do projeto, existem muitos métodos que ajudam a seguir o SRP, um deles é a divisão em classes e a minimização do número de "funções" pelas quais cada classe é responsável (divisão celular e sua especialização no corpo).
Aqui eu gostaria de escrever um conjunto de técnicas para manter o SRP, mas este trabalho ainda não foi concluído (espero que "alcance as mãos"). Nas áreas óbvias onde você pode procurar esses truques:
- padrões de design;
- usando diferentes ramificações especializadas de componentes, em vez de criar um componente que satisfaça todos os métodos de aplicação (bifurcação no GitHub).
Princípio Aberto / Fechado (OCP) Princípio Aberto / Fechado
É ideal planejar o desenvolvimento do código para que o programador implemente novas tarefas, é necessário adicionar um novo código, enquanto o código antigo não precisa de alterações. O código deve estar aberto (Aberto) para adicionar e fechado (Fechado) para alterar.
O objetivo deste princípio é minimizar os custos de mão-de-obra e eliminar erros implícitos introduzidos devido aos seguintes invariantes no desenvolvimento:
- [1], [2], [3] descrito anteriormente,
- Para implementar uma nova tarefa, o programador pode adicionar novos [componentes] ou alterar o comportamento dos antigos [componentes],
- a adição de [componente] requer verificação no local do novo uso e custa o tempo do programador
- a mudança no comportamento do [componente] causado pela nova tarefa requer verificação no local do novo uso e em todos os locais de uso antigo, o que também causa o consumo de tempo do programador e, no caso do [componente] publicado, o trabalho de todos os programadores que usaram o [componente].
é aconselhável escolher uma opção para implementar uma nova tarefa, minimizando o tempo gasto pelo programador.
Mais frequentemente, na prática do desenvolvimento de software, o custo da adição é muito menor que o custo da mudança, o que torna óbvio o benefício de usar o princípio [Aberto-Fechado]. Ao mesmo tempo, existem muitas técnicas para manter a arquitetura do programa em um estado em que a implementação de uma nova tarefa se resume a adicionar apenas [componentes]. Esse trabalho com arquitetura também requer tempo do programador, mas a prática em grandes projetos mostra muito menos do que usar a abordagem de alterar procedimentos antigos. E, é claro, essa descrição do desenvolvimento é idealização. Quase não há implementação da tarefa apenas adicionando ou alterando. Na vida real, é usada uma mistura dessas abordagens, mas o OCP enfatiza o benefício de usar a abordagem de adição.
E aqui eu gostaria de escrever um conjunto de técnicas para manter o OCP. Nas áreas óbvias onde você pode procurar esses truques:
- padrões de design;
- bibliotecas DLL e opções para sua distribuição, atualização e desenvolvimento de funcionalidades;
- desenvolvimento de bibliotecas e objetos COM neles;
- desenvolvimento de linguagens de programação e suporte a código previamente escrito;
- desenvolver o sistema legislativo do estado.
Princípio da Substituição de Liskov (LSP) Barbara Liskov Princípio da Substituição
Este princípio limita o uso da extensão da interface base [base] à implementação, declarando que cada implementação da interface base deve ter um comportamento como interface base. Ao mesmo tempo, a interface básica corrige o comportamento esperado nos locais de uso. E a presença no comportamento de implementação de uma diferença do comportamento esperado, fixado pela interface base, levará à possibilidade de violação do invariante [2].
Esse princípio é baseado e refina a técnica de design com base na abstração. Nesta abordagem, uma abstração é introduzida - algumas propriedades básicas e características de comportamento de muitas situações são corrigidas. Por exemplo, [procedimento de componente] "Mover para a posição anterior" para situações: "Cursor no texto", "Livro em uma prateleira", "Elemento em uma matriz", "Pés na dança", etc. E atribuído a este [componente] ( frequentemente pela experiência cotidiana e sem formalização) alguns pré-requisitos e comportamentos, por exemplo: “A presença de um objeto móvel”, “Repita várias vezes”, “Presença da ordem dos elementos”, “Presença de posições fixas dos elementos”. O LSP exige que, ao adicionar uma nova situação de uso para [component], todos os pré-requisitos e limitações da base sejam atendidos. E a situação "grãos em uma lata de açúcar" não pode ser descrita por essa abstração, embora os grãos, é claro, tenham uma posição, há posições em que os grãos estiveram antes e é possível movê-los neles - não há apenas posições fixas de elementos.
O objetivo deste princípio é eliminar erros implícitos introduzidos devido aos seguintes invariantes no desenvolvimento:
- [1], [2], [3] descrito anteriormente,
- o [procedimento] básico descreve um comportamento que é útil em um grande número de situações, definindo as restrições necessárias para sua aplicabilidade,
o [procedimento] desenvolvido da implementação da base deve cumprir todas as suas limitações, incluindo implícitas controladas de maneira rígida (fornecidas informalmente).
Muitas vezes, um exemplo com um retângulo ([base]) e um quadrado (implementação) é dado para descrever esse princípio. class CSquare : public CRectangle
situação class CSquare : public CRectangle
. Em [base], são introduzidas operações para trabalhar com largura e altura (Set (Get) Width, Set (Get) Height)). Na implementação do CSquare, essas operações de Conjunto são forçadas a alterar os dois tamanhos do objeto. Sempre me faltava a explicação de que a seguinte restrição é definida "informalmente" na [base]: "a capacidade de usar Largura, Altura independentemente". Na implementação do CSquare, é violada e, em locais de uso, uma sequência simples de ações com base no uso dessa independência: r.SetWidth(r.GetWidth()*2); r.SetHeight(r.GetHeight()*2)
r.SetWidth(r.GetWidth()*2); r.SetHeight(r.GetHeight()*2)
- para implementação, o CSquare aumentará os dois tamanhos em 4 vezes, em vez de 2 vezes assumidas para CRectangle.
IMHO este princípio indica a dificuldade de rastrear tais restrições informais, as quais, com grande utilidade e alta frequência de uso da abordagem de desenvolvimento "implementação da base", requerem atenção especial.
Princípio de Segregação de Interface (ISP), princípio de separação de interfaces; Princípio de inversão de dependência (DIP) Princípio de inversão de dependência
Esses dois princípios são muito próximos na área de seus requisitos. Ambos implicitamente implicam a utilidade de usar a menor interface básica possível como uma ferramenta para a interação de dois [componentes]: "cliente" e "servidor" - esses nomes são escolhidos simplesmente para identificação. Nesse caso, as informações gerais usadas pelos [componentes] estão concentradas na interface base. Um [componente] ("servidor") implementa a implementação da interface base, o outro [componente] ("cliente") refere-se a esta implementação.
O objetivo desses princípios é minimizar as dependências do componente, permitindo alterações independentes em seu código, se ele não alterar a interface subjacente. A independência das alterações de componentes reduz a complexidade e a mão-de-obra se os componentes atenderem aos requisitos do princípio SRP. Uma abordagem semelhante é possível porque existem os seguintes invariantes no desenvolvimento:
- [1], [2], [3] descrito anteriormente,
- cada [componente] inerente ao seu comportamento forma os limites de seu uso,
- em cada local de uso do [componente] todas as suas restrições podem estar envolvidas,
- a consequência [componente] básica da definição tem menos complexidade e número de restrições do que a implementação [componente],
- qualquer alteração no [componente] altera suas limitações e exige a verificação de todos os locais de uso, o que causa gasto de tempo de um programador,
os locais de uso do [componente] base não exigem verificação após fazer alterações na implementação do [componente].
É claro que é aconselhável minimizar o "tamanho" da interface base descartando funcionalidades e restrições não utilizadas, limitando assim a implementação do [componente] pelo princípio do (LSP) menos
O princípio do ISP enfatiza a necessidade de separação (segregação) da interface do "servidor", se nem toda a sua funcionalidade publicada for usada por esse "cliente". Nesse caso, apenas a [base] exigida pelo cliente é alocada e a minimização das informações restritivas em conjunto é garantida.
E aqui eu gostaria de escrever um conjunto de técnicas para manter o DIP. Nas áreas óbvias onde você pode procurar esses truques:
- separação da descrição da classe em partes públicas e privadas (e outros princípios da OOP),
- descrição da interação com uma biblioteca dinâmica com um conjunto limitado de funções e descritores de objetos,
- usando um gabinete de arquivos como uma interface para acessar uma biblioteca de livros.
Voltando ao título, explicarei por que "não entender" está selecionado. A negação é adicionada, a fim de enfatizar, por meio de erros, a regra útil de muito sofrimento e IMHO. É melhor não entender e, portanto, não usar a tecnologia, do que entendê-la mal, confiar com fé, gastar seus recursos no uso da tecnologia e, como resultado, não obter nenhum escape útil, exceto a complacência e a possibilidade de se gabar do envolvimento na tecnologia da moda.
Obrigado pela atenção.
Referências