Projeto de Software e Evolução OOP

Dominando as receitas para o desenvolvimento efetivo de um projeto de software, tentei descobrir por mim as razões que tornam úteis o uso dos princípios de desenvolvimento da arquitetura SOLID (artigo Como não entender os princípios de desenvolvimento da arquitetura SOLID ).


Uma análise desses princípios permitiu destacar vários padrões-chave e elementos básicos que existem no desenvolvimento. Eles nos permitiram descrever, entender e implementar o SOLID em trabalho real com um projeto de software.


Tornou-se interessante realizar uma análise da aplicabilidade desses conceitos para paradigmas de programação geralmente aceitos, por exemplo, para OOP. Bem, se o resultado deste trabalho for útil para você.


imagem


Hoje, existem muitas abordagens para o design e a subsequente implementação de projetos de software. Os mais exigidos no trabalho com grandes projetos de software são: programação estrutural , programação funcional , programação orientada a objetos .


Para mim, tornou-se interessante analisar as causas dessas abordagens de design. E no processo de análise, a descoberta inesperada foi o fato de que todas elas são implicitamente baseadas na seguinte premissa:


          ,      . 

Desenvolvimento de Projetos de Software


O que é um projeto sem a necessidade de desenvolvimento? Tais projetos raramente são encontrados e são caracterizados principalmente por pagamentos rápidos por peça, sem quaisquer obrigações subsequentes por parte do programador, por exemplo:


  • um pequeno projeto que pode ser escrito com uma abordagem;
  • Um projeto sem código estruturalmente complexo, sobrecarregado com um grande número de relacionamentos;
  • software sem a necessidade de suporte e suporte ao usuário.

Em tais situações, os esforços do programador para manter, por exemplo, uma abordagem orientada a objetos são desperdiçados. Muitas vezes acontece que me encontro em uma lição sem sentido durante o desenvolvimento de um utilitário de console único, quando repentinamente percebo que escrever o texto da 4ª série neste projeto me atrasou 15 minutos e não me aproximou do resultado. O mais triste é que todas as classes que dificilmente foram escritas nesses projetos são esquecidas e não reutilizadas, ou seja, elas não facilitam nosso trabalho no futuro.


Em todas as outras situações, o programador, minimizando seu trabalho, deve desenvolver um projeto estruturalmente complexo, ou seja:


  • Corrija os erros analisando o código e localizando os locais onde esses erros são gerados.
  • Introduzir novas funcionalidades, mantendo a funcionalidade de todos os recursos disponíveis anteriormente. Ao fazer isso, use o código existente (escrito e testado) na implementação dessas novas tarefas.
  • Forneça suporte no uso de um produto de software.
  • Execute uma descrição e coordenação da funcionalidade de todas as versões do projeto.
  • Mantenha todos os formatos de dados usados ​​pelo projeto (mesmo desatualizados) operacionais.
  • E execute muitas outras tarefas que aparecem no confronto com os concorrentes causadas pela mudança de estruturas ou pelo fim do suporte para sistemas operacionais obsoletos ...

Se você procurar analogias para o desenvolvimento de um projeto de software, poderá se lembrar da evolução de uma espécie biológica.


      "".     -       .       -  . 

O trabalho do programador não é fácil, mas o programador tem um "auxiliar". Esse ajudante está escondido em algum lugar profundo da estrutura do nosso mundo, no qual existem dois recursos:


  • a capacidade de escrever um algoritmo útil e usá-lo para muitas tarefas semelhantes,
  • a presença de muitas tarefas similares em sua solução.

Esse algoritmo, útil em muitos campos, será chamado de algoritmo universal por questões de brevidade. Sua implementação para um campo específico de aplicação pode ser chamada de especialização, uma vez que o processo de refinar o algoritmo para uso em um campo restrito de aplicação é semelhante à especialização evolutiva das células em um organismo vivo.


Obviamente, para criar um algoritmo, é necessário identificar recursos que garantam a aplicabilidade do algoritmo. Esses sinais devem ser procurados nos dados de entrada e na descrição da situação inicial (contexto). Para criar um algoritmo universal , é necessário em cada área de assunto, que possui seus próprios conjuntos de sinais de dados e situações, identificar sinais de aplicabilidade idênticos para todas as áreas. Todos os outros sinais que não fornecem aplicabilidade são ignorados pelo algoritmo universal . Formalizando o algoritmo universal , chegamos à necessidade de usar abstração - um dos princípios mais importantes da OOP. Além disso, OOP é caracterizado por uma ênfase apenas na abstração de dados.


Aqui vou tentar escrever exemplos de uso de abstração de diferentes áreas.


AbstraçãoAlgoritmosCampo de aplicação
Números naturaisAlgoritmos de cálculo quantitativoTarefas de contabilização de valores econômicos
Característica de massa do corpo do materialAlgoritmos para comparar a quantidade de substânciaTarefas de comparação do valor de um produto não responsável
Interface com operações para uma coleção de elementos: rastreamento completo, comparação e troca de posiçõesAlgoritmos de Classificação de ColeçãoProgramação
A interface das mesmas operações para o "nó final" e o "nó ramo" na árvoreAlgoritmos baseados no padrão de design do LayoutDesenvolvimento de um projeto de software complexo
Conceito-chave "Empregado"Redação na seção "Contrato de trabalho"Código do Trabalho

Bloco de construção de um projeto de software


Usando várias técnicas de abstração, o programador implementa o algoritmo na forma de uma seção de código, que é um elemento separado e completo de seu trabalho. Esse elemento, dependendo da linguagem de programação usada, pode ser uma função, um objeto e uma sequência de instruções. Para facilitar a discussão, chamaremos esse fragmento de código de palavra " componente ".


Componente - um pedaço de código (procedimento, classe, componente de implantação etc.):


  • que implementa algum algoritmo completo que funciona em determinadas situações iniciais e com certos dados de entrada,
  • que pode ser usado várias vezes em um projeto (ainda melhor várias vezes em projetos diferentes),
  • todas as instruções localizadas próximas e visualizadas sem a necessidade de operações de pesquisa adicionais no ambiente de desenvolvimento,
  • alterações nas quais o programador executa de forma relativamente independente em relação ao restante do código.

Padrões no desenvolvimento de um projeto de software


Usando o termo componente , torna-se possível formular um conjunto de leis simples que existem no desenvolvimento de um projeto de software. Vou apresentar esses padrões na forma das seguintes declarações, divididas em 3 categorias.


  1. Instruções que descrevem as propriedades de um componente .
    1.1 Um componente corretamente escrito é necessariamente usado e com mais frequência várias vezes.
    1.2 Em todos os locais em que o componente é usado, é esperado um comportamento constante, levando a um resultado repetitivo.
    1.3 Ao usar o componente em vários locais, o resultado deve satisfazer cada local de uso.
    1.4 O comportamento incorporado no componente cria restrições nos locais de uso desse componente .
    1.5 Em cada local de uso do componente , todas as suas restrições podem estar envolvidas.
    1.6 Qualquer alteração em um componente altera suas limitações e requer a verificação de todos os locais de uso, o que faz com que o programador perca tempo.
    1.7 É aconselhável gravar o componente na forma de código em uma instância, ou seja, é necessário eliminar a duplicação do mesmo código. Isso reduzirá o número de edições ao fazer uma alteração em um componente .
  2. Instruções que descrevem padrões na implementação de uma nova tarefa pelo programador.
    2.1 É aconselhável escolher uma opção para implementar uma nova tarefa enquanto minimiza o tempo gasto pelo programador.
    2.2 Para implementar uma nova tarefa, um programador pode adicionar novos componentes ou alterar o comportamento dos componentes antigos.
    2.3 A adição de um componente basicamente exige verificação apenas no local de novo uso e gera um tempo mínimo para o programador.
    2.4 De acordo com a declaração [1.6], uma mudança no comportamento de um componente causado por uma nova tarefa requer verificação no local do novo uso e em todos os locais de uso antigo, o que cria custos adicionais de tempo do programador em comparação com a situação na declaração [2.3]. No caso de um componente publicado , isso requer o trabalho de todos os programadores que usam o componente modificado.
  3. Instruções que descrevem padrões na interação de algoritmos universais e suas especializações:
    3.1 Há uma oportunidade de escrever um componente base (o nome é introduzido por analogia com a classe base e, por uma questão de brevidade, usaremos a palavra " base "). A base atende apenas aos recursos mais importantes de algum algoritmo universal .
    3.2 É possível escrever uma componente - especialização (daqui por diante, usaremos a palavra " especialização "). A especialização complementa o algoritmo universal da base , tornando-o aplicável em uma área específica de uso.
    3.3 A base , como segue as declarações [3.1], [3.2], possui menos complexidade e menos restrições de aplicativos que a especialização .
    3.4 De acordo com a declaração [1.7], é aconselhável desenvolver especialização sem duplicação do código do algoritmo universal do banco de dados .
    3.5 Os locais de uso do banco de dados não requerem verificação após fazer alterações na especialização formada corretamente.

Conceitos de programação orientada a objetos


Tentarei, usando as instruções acima, analisar os conceitos básicos da programação orientada a objetos. Essa análise ignora o conceito de abstração , uma vez que já foi descrito anteriormente na formalização do método de construção de um algoritmo universal .


Classe, Objeto


Esses conceitos de POO reforçam a viabilidade de usar um tipo especial de componente descrito por uma combinação de alguns dados e métodos internos para trabalhar com esses dados. Todas as declarações do grupo [1] e [2] são traduzidas para OOP, para as quais o termo componente é substituído pelo conceito de classe .


Ao mesmo tempo, à primeira vista, as relações de uma classe e um objeto são esgotadas pelo grupo de instruções [3], no qual a base é substituída pelo conceito de classe e a implementação pelo conceito de objeto . Além disso, a implementação é dinâmica, ou seja, mutável durante a execução do programa.


Encapsulamento


O conceito de " encapsulamento " pode ser considerado de dois "lados".


O primeiro lado do conceito de " encapsulamento " é o isolamento do componente de outras partes do código. Essa propriedade permite que o programador faça operações em áreas do código localizadas "close" para fazer alterações no componente . Ou seja, para minimizar o tempo gasto pelo programador, excluindo do trabalho a pesquisa e análise de elementos interativos díspares do programa. Esse lado é definido pelas propriedades do componente após sua definição.


O segundo lado do conceito de " encapsulamento " é a ocultação da implementação interna do componente . Essa ocultação é possível usando os conceitos de base e implementação descritos no grupo de declarações [3]. Para fazer isso, os métodos de classe pública são identificados com a base e os métodos de classe privada e protegida são identificados com a implementação . Nos locais de uso, são usadas as restrições formadas pela base e, portanto, torna-se possível fazer alterações na implementação que não estão relacionadas às restrições básicas . E essas mudanças na implementação não precisam ser verificadas nos locais onde o banco de dados é usado [3.5], o que minimiza o trabalho do programador.


Vale ressaltar que o conceito de " encapsulamento " tem uma analogia na biologia. Este primeiro processo é semelhante às funções biológicas da " membrana celular ".


Herança


O conceito de " herança " continua a reforçar a importância do uso de uma combinação de base + implementação . Para isso, no grupo de declarações [3], é necessário identificar os métodos da classe pai com a base e identificar os métodos da classe sucessora com a implementação .


Na sua implementação, o conceito de “ herança ” permite o uso da declaração [2.3], ou seja, use a adição de código em vez de alterá-lo e duplicá-lo. Nesse caso, é necessário excluir a duplicação do algoritmo básico . No entanto, uma abordagem que usa herança para especializar um algoritmo universal tem um sinal de menos. Essa desvantagem é a presença de dois componentes fortemente conectados, difíceis de mudar independentemente. Esses relacionamentos de dependência são gerados pelo relacionamento pai-filho.


Existem muitas maneiras alternativas de usar o pacote básico de implementação . Vou dar mais exemplos de tais métodos.


BaseImplementaçãoCampo de aplicação
Métodos de classe públicaMétodos de classe privadaEncapsulamento
Métodos protegidos da classe paiMétodos de classe de herançaHerança
Interface de biblioteca dinâmicaFuncionalidade de biblioteca dinâmicaComponente = biblioteca dinâmica
Métodos e classes de gabarito (generalizados) (gabarito, genérico)Instanciando um modelo com argumentos especificadosProgramação geral
Métodos genéricos que aceitam delegadosEspecialização de métodos indicando procedimentos específicos de processamentoProcedimentos para classificar ou formar uma árvore, indicando o método para avaliar a ordem dos elementos
Classes que permitem interação com o modelo de visitanteFormação de "Visitante" com a funcionalidade necessáriaPadrão de design do visitante
Painel de controle de NPPO conjunto de automação e equipamentos de usinas nuclearesOcultação da complexidade do sistema do operador NPP

Ao mesmo tempo, percebo que, para o conceito de " herança " da OLP, também é possível encontrar uma analogia nos processos de evolução biológica. Na biologia, o termo " hereditariedade " é usado para isso.


Polimorfismo


Na minha opinião, o conceito de " polimorfismo " é o segundo lado ao analisar o procedimento para criar um algoritmo universal . O primeiro lado ( abstração ) é uma visão do ponto de vista de como criar um algoritmo universal . Ao mesmo tempo, quando analisamos o algoritmo universal do ponto de vista do usuário, obtemos um registro do conceito de polimorfismo . Ou seja, o polimorfismo é uma capacidade útil de uma função ( componente ) para processar dados de vários tipos. Adicionar esse conceito ao POO reforça a utilidade do uso de um algoritmo universal no desenvolvimento de um projeto de software.


As implementações de polimorfismo em diferentes linguagens de programação são muito diferentes. No artigo da Wikipedia para polimorfismo , dependendo de sua implementação, existem 4 subtipos: paramétrico, inclusão (ou subtipos), sobrecarga, tipo de conversão. Essas implementações têm diferenças significativas, mas todas estão unidas por um objetivo - isto é, escrever um algoritmo universal que não precisará ser duplicado para sua especialização específica.


E desta vez, quase sem surpresa, ele encontrou uma analogia para o conceito de " polimorfismo " na biologia. O nome desse termo biológico coincide totalmente com o conceito de POO. " Polimorfismo " - a capacidade de um organismo existir em estados com diferentes estruturas internas ou em diferentes formas externas.


Conclusão


Assim, quase todos os conceitos básicos de POO podem ser representados como um conjunto de declarações simples formadas com base nas leis de desenvolvimento de um projeto de software. Além disso, para OOP, o termo componente é identificado com o conceito de classe . Se destacarmos um significado diferente para o termo componente , por exemplo, uma função , é possível formular os conceitos básicos de programação funcional .


No processo de redação do artigo, foram encontradas analogias biológicas para os conceitos utilizados na programação. Essas analogias aparecem devido à semelhança dos métodos de desenvolvimento de um produto de software e a alguns processos de evolução biológica.


IMHO, é aconselhável considerar essas duas áreas científicas juntas. Nesse caso, pode ser possível realizar a transferência de leis de um setor para outro e, assim, garantir o desenvolvimento da tecnologia da informação e das descrições formais dos processos biológicos.


Obrigado pela atenção.


Comentários


Ficaria muito grato pelo feedback, sugestões e sugestões, pois elas me ajudam a ajustar a direção do desenvolvimento do trabalho nesta área.


Referências



Editado por Borisova M.V.

Source: https://habr.com/ru/post/pt448026/


All Articles