Notas de leitura "C++ Advanced Programming" (cinco, seis: design orientado a objetos e código reutilizável de design)

1. Referências

2. Recomenda-se a leitura do livro "21 Days to Learn C++" para começar. O link das notas é o seguinte

1. Processar o pensamento

  • Linguagens procedurais (como C) dividem o código em pequenos pedaços , cada um dos quais (em teoria) realiza uma única tarefa. Se não houvesse procedimentos em C, todo o código estaria concentrado em main() e o código seria difícil de ler
  • O computador não se importa se o código está em main() ou se está dividido em pedaços menores com nomes descritivos e comentários. Um procedimento é uma abstração que existe para ajudar programadores e pessoas que leem ou fazem manutenção de código. Esse conceito se baseia em uma questão fundamental sobre programas: o que o programa faz? Responder a esta pergunta em palavras é pensar processualmente

2. Pensamento orientado a objetos

  • Ao contrário da abordagem procedural, que se baseia na pergunta "o que o programa faz", a abordagem orientada a objetos faz outra pergunta: quais objetos reais são simulados ? O conceito básico de OOP (Programação Orientada a Objetos) é que o programa não deve ser dividido em tarefas, mas sim em modelos de objetos naturais. Isso pode parecer abstrato a princípio, mas a ideia fica mais clara ao considerar objetos reais em termos de componentes de classe, propriedades e comportamentos

2.1 classe

  • Uma classe distingue um objeto de sua definição
  • Uma classe simplesmente encapsula informações usadas para definir uma classe de objetos
  • Todos os objetos pertencem a uma classe particular, um objeto é uma instância de uma classe

2.2 Componentes

  • Essencialmente, os componentes são semelhantes às classes, mas menores e mais específicos
  • Se você considerar um objeto real complexo, poderá ver que ele é composto de muitos componentes pequenos

2.3 Propriedades

  • Propriedades distinguem um objeto de outros objetos
  • Os atributos de classe são compartilhados por todos os membros da classe, enquanto todos os objetos de uma classe têm atributos de objeto, mas com valores diferentes
  • Os atributos são usados ​​para descrever as características de um objeto e responder à pergunta "Por que esse objeto é diferente?"

2.4 Comportamento

  • O comportamento responde a duas perguntas: "o que um objeto faz" e "o que ele pode fazer a um objeto"
  • Na programação orientada a objetos, muito do código funcional é movido de procedimentos para classes. Ao estabelecer objetos com certos comportamentos e definir como os objetos interagem, OOP fornece um mecanismo mais rico para vincular o código aos dados nos quais o código opera.
  • O comportamento da classe é implementado por métodos de classe

3. Relações entre objetos

3.1 "tem um" relacionamento

  • O esquema para um relacionamento "tem-um" ou relacionamento agregado é que A tem um B ou A contém um B. Nesse tipo de relacionamento, um objeto é considerado parte de outro objeto. Os componentes, conforme definido anteriormente, normalmente representam um relacionamento "tem um", porque os componentes representam objetos que compõem outros objetos

3.2 "é um" relacionamento (herança)

  • O relacionamento "é um" é um conceito muito fundamental na programação orientada a objetos e, como tal, tem muitos nomes, incluindo derivados, subclasses, estendidos e herdados . As classes modelam o fato de que o mundo real contém objetos com propriedades e comportamentos, e a herança modela o fato de que esses objetos geralmente são organizados de maneira hierárquica. "é um" ilustra essa relação hierárquica. Basicamente, o padrão de herança é: A é um B, ou A é realmente muito semelhante a B
  • Uma classe Animal pode ser definida para encapsular as propriedades (tamanho, área de convivência, comida, etc.) e comportamento (andar, comer, dormir) comuns a todos os animais. Um animal específico (como um macaco) torna-se uma subclasse de Animal, porque um macaco contém todas as características de um animal e também possui outras características que o tornam único

insira a descrição da imagem aqui

  • Quando há um relacionamento "é-um" entre as classes, um dos objetivos é colocar uma funcionalidade comum em uma classe base que outras classes possam estender . Se todas as subclasses tiverem código semelhante ou idêntico, considere colocar parte ou todo o código na classe base. Dessa forma, as alterações necessárias podem ser feitas em um só lugar e as subclasses futuras obtêm essas funcionalidades compartilhadas "de graça"
3.2.1 Tecnologia de herança
  • Adicionar função
    • As classes derivadas podem adicionar funcionalidade à classe base . Por exemplo, um macaco é um animal que pode ser pendurado em uma árvore. Além de todos os comportamentos dos animais, os macacos também possuem o comportamento de se movimentar entre as árvores, ou seja, a classe Monkey possui um método swingFromTrees(), e esse comportamento só existe na classe Monkey
  • substituir função
    • Uma classe derivada pode substituir ou anular completamente o comportamento de uma classe pai . Por exemplo, a maioria dos animais anda, então a classe Animal pode ter um comportamento de movimento que simule a caminhada. Mas o canguru é um animal que se move pulando em vez de andar, as outras propriedades e comportamentos da classe base Animal ainda se aplicam, a classe derivada do Canguru só precisa mudar a forma como o comportamento do movimento funciona

    Claro, se você substituir toda a funcionalidade da classe base, isso pode significar que o modo de herança não está correto, a menos que a classe base seja uma classe base abstrata : a classe base abstrata forçará todas as subclasses a implementar Todos os métodos implementado, incapaz de criar uma instância de uma classe base abstrata

  • adicionar atributo
    • Além de herdar propriedades da classe base, as classes derivadas podem adicionar novas propriedades . Os pinguins possuem todos os atributos dos animais, além do atributo tamanho do bico (tamanho do bico)
  • substituir atributo
    • Semelhante aos métodos de substituição, C++ fornece métodos para substituir propriedades . No entanto, fazer isso geralmente é inapropriado porque ocultaria as propriedades da classe base , por exemplo, uma classe base poderia atribuir um valor a uma propriedade com um nome específico e uma classe derivada poderia atribuir outro valor a essa propriedade
3.2.2 Polimorfismo e reutilização de código
  • Polimorfismo significa que objetos com propriedades e métodos padrão podem ser usados ​​de forma intercambiável
  • Ao simular um zoológico, você pode percorrer programaticamente todos os animais no zoológico e permitir que cada animal se mova uma vez. Como todos os animais são membros da classe Animal, todos eles sabem como se mover. Certos animais substituem o comportamento de movimento, mas é aí que ele brilha: o código apenas diz a cada animal para se mover, não sabe ou não se importa que tipo de animal é, todos se movem à sua maneira
  • Há outro motivo para usar a herança além do polimorfismo e, geralmente, é apenas para aproveitar o código existente . Por exemplo, se você precisa de uma aula de reprodução de música com um efeito de eco e um colega escreveu uma aula que toca música, mas não tem outros efeitos, você pode estender essa classe existente e adicionar uma nova função de eco

3.3 não é um relacionamento

  • Ao considerar o relacionamento entre as classes, você deve considerar se realmente existe um relacionamento entre as classes. Os problemas surgem quando há uma relação aparente entre coisas reais, mas não no código . Hierarquias OO (orientadas a objetos) precisam modelar relacionamentos funcionais, não fabricá-los artificialmente. As relações mostradas no diagrama abaixo fazem sentido como conjuntos de conceitos ou hierarquias, mas não representam relações significativas no código
    insira a descrição da imagem aqui

A melhor maneira de evitar herança desnecessária é dar um design básico primeiro. Escreva as propriedades e os comportamentos que planeja definir para cada classe e classes derivadas. Se você achar que uma determinada classe não possui suas próprias propriedades ou métodos específicos, ou que todas as propriedades e métodos de uma determinada classe são substituídos por classes derivadas, desde que essa classe não seja a classe base abstrata acima mencionada, você deve reconsiderar o projeto

3.4 Hierarquia

  • Assim como a classe A pode ser a classe base da classe B e a classe B pode ser a classe base da C, as hierarquias orientadas a objetos podem modelar relacionamentos multiníveis semelhantes . Ao escrever o código para cada classe derivada, muito do código provavelmente será semelhante. Neste ponto, você deve considerar deixá-los ter uma classe pai comum

insira a descrição da imagem aqui

  • Uma boa hierarquia orientada a objetos pode fazer o seguinte:
    • permitir relacionamentos funcionais significativos entre as classes
    • Coloque funcionalidade comum em classes base, permitindo assim a reutilização de código
    • Evite subclasses reescrevendo muito as funções da classe pai, a menos que a classe pai seja uma classe abstrata

3.5 Herança múltipla

  • Na herança múltipla, uma classe pode ter várias classes base (herança múltipla geralmente deve ser evitada)

insira a descrição da imagem aqui

4. Abstração

4.1 Interface e Implementação

  • A chave para a abstração é efetivamente separar interface e implementação
    • A implementação é o código usado para realizar uma tarefa, a interface é a maneira como outros usuários usam o código
    • Em C, o arquivo de cabeçalho que descreve a função da biblioteca é uma interface, na programação orientada a objetos, a interface de uma classe é uma coleção de propriedades e métodos públicos
    • Uma boa interface contém apenas comportamento público, as propriedades/variáveis ​​de uma classe nunca devem ser públicas, mas podem ser expostas por meio de métodos públicos chamados getters e setters

4.2 Decidir sobre a interface exposta

  • Ao projetar classes, como outros programadores interagem com seus objetos é um problema. Em C++, as propriedades e métodos de uma classe podem ser públicos (públicos), protegidos (protegidos) e privados (privados).
    • público significa que outro código pode acessá-los
    • protegido significa que outro código não pode acessar essa propriedade ou comportamento, mas as subclasses podem
    • privado significa que não apenas outro código não pode acessar essa propriedade ou comportamento, nem as subclasses
  • Projetar interfaces públicas é escolher quais devem ser públicas
    • Considere os usuários: indivíduos, outros membros da equipe, usuários, etc.
    • Finalidade da consideração: API, classe de utilitário ou biblioteca, interface de subsistema, interface de componente
    • Pense no futuro: não crie uma classe de log abrangente se o uso futuro for desconhecido, pois isso complica desnecessariamente o design, a implementação e a interface pública

5. Como projetar código reutilizável

  • Existem dois objetivos principais para o código reutilizável: o código deve ser comum e o código deve ser fácil de usar

5.1 Usando abstrações

  • Usar a abstração é bom para você e para os clientes que usam o código
    • Os clientes se beneficiam porque não precisam se preocupar com detalhes de implementação: eles podem aproveitar a funcionalidade fornecida sem ter que entender como o código realmente funciona
    • Você ganha o benefício porque pode modificar o código subjacente sem alterar a interface. Isso permite que o código seja atualizado e revisado sem exigir que os clientes alterem seu uso (ou mesmo reconstruam o executável se forem usadas bibliotecas de vínculo dinâmico)
  • Às vezes, as bibliotecas exigem que o código do cliente salve as informações retornadas por uma interface para transmiti-las a outras interfaces. Às vezes, essa informação é chamada de identificador e geralmente é usada para rastrear certas instâncias cujo estado no momento da chamada precisa ser lembrado
    • Se o design da biblioteca exigir alças, não exponha as partes internas da alça . O identificador pode ser colocado em uma classe opaca, o programador não pode acessar diretamente os membros de dados internos desta classe, nem pode ser acessado por meio de getters ou setters públicos
    • Não exija que o código do cliente altere as variáveis ​​dentro do identificador . Um exemplo de design ruim é uma biblioteca que requer a configuração de um membro específico de uma estrutura na qual o identificador deve ser opaco para permitir o registro de erros

5.2 Construindo Código Idealmente Reutilizável

5.2.1 Evite combinar conceitos não relacionados ou conceitos logicamente independentes
  • Ao projetar componentes, você deve se concentrar em uma única tarefa ou em um grupo de tarefas, ou seja, "alta agregação" , também conhecido como SRP (Single Responsibility Principle, Single Responsibility Principle). Não combine conceitos não relacionados, como geradores de números aleatórios e analisadores XML

    • Essa estratégia de programação imita os princípios de design do mundo real de peças independentes intercambiáveis . Por exemplo, você poderia escrever uma classe Car na qual colocaria todas as propriedades e o comportamento do motor. Mas o motor é um componente separado, não vinculado ao resto do carro. O motor pode ser removido de um carro e instalado em outro. Um design razoável seria adicionar uma classe Engine que contém todas as funcionalidades relacionadas ao mecanismo. Doravante, a instância Car conterá a instância Engine
  • Divida o programa em subsistemas lógicos

    • Projetar subsistemas como componentes discretos que podem ser reutilizados individualmente, ou seja, "baixo acoplamento". Por exemplo, ao projetar um jogo online, a rede e a GUI devem ser colocadas em subsistemas separados para que um componente possa ser reutilizado sem envolver o outro. Supondo que um jogo autônomo seja escrito agora, o subsistema de interface gráfica pode ser reutilizado, mas a função de rede não é necessária . Da mesma forma, um programa de compartilhamento de arquivos ponto a ponto pode ser projetado, caso em que o subsistema de rede pode ser reutilizado, mas nenhuma funcionalidade GUI é necessária
  • Separe conceitos lógicos com hierarquias de classe

    • Além de dividir os programas em subsistemas lógicos, deve-se evitar o agrupamento de conceitos não relacionados no nível da classe. Por exemplo, suponha que você queira escrever uma classe para um carro autônomo. Você decide escrever a classe base para o carro primeiro e, em seguida, colocar toda a lógica de direção autônoma diretamente lá. Mas e se apenas carros não autônomos forem necessários no programa? Nesse ponto, toda a lógica relacionada à autocondução falhará, e o programa deverá vincular-se a bibliotecas que podem ser evitadas, como a biblioteca de visão e a biblioteca LIDAR. A solução é criar uma hierarquia de classes com carros autônomos como derivado do tipo de carros comuns

insira a descrição da imagem aqui

  • Separando conceitos lógicos com agregação
    • A agregação pode ser usada para separar funcionalidade não relacionada ou funcionalidade relacionada, mas independente, quando a abordagem de herança não for apropriada . Por exemplo, suponha que você queira escrever uma classe Family para armazenar os membros da família. Claramente, uma estrutura de dados em árvore é uma estrutura ideal para armazenar essas informações. Em vez de integrar o código para a estrutura de dados da árvore na classe Family, escreva uma classe Tree separada que possa conter e usar instâncias Tree
5.2.2 Usando modelos para estruturas e algoritmos de dados genéricos
  • O conceito de templates C++ permite a criação de estruturas genéricas na forma de tipos ou classes. Por exemplo, suponha que você escreva código para uma matriz de números inteiros. Se você quiser usar arrays duplos no futuro, precisará reescrever e duplicar todo o código. O conceito de templates transforma um tipo em um parâmetro que precisa ser especificado, para que um corpo de código possa ser criado para qualquer tipo. Os modelos permitem escrever estruturas de dados e algoritmos que se aplicam a qualquer tipo

    • O exemplo mais simples é a classe std::vector, que faz parte da biblioteca padrão C++. Para criar um vetor do tipo inteiro, escreva std::vector<int>; para criar um vetor do tipo double, escreva std::vector<double>
  • problema de modelo

    • Os modelos não são perfeitos. Primeiro, a sintaxe é confusa , especialmente para alguém que nunca usou modelos. Em segundo lugar, o modelo requer o mesmo tipo de estrutura de dados e apenas objetos do mesmo tipo podem ser armazenados em uma estrutura
  • Modelos e Herança

    • Use modelos se você pretende fornecer a mesma funcionalidade para tipos diferentes . Por exemplo, se você deseja escrever um algoritmo de classificação genérico que funcione em qualquer tipo, você deve usar o modelo
    • Se você deseja criar um contêiner que possa armazenar qualquer tipo, deve usar um modelo . O conceito-chave é que uma estrutura ou algoritmo com modelo tratará todos os tipos da mesma maneira. No entanto, os modelos podem ser especializados para tipos específicos para tratá-los de maneira diferente, se necessário
    • A herança deve ser usada quando for necessário fornecer comportamento diferente de tipos relacionados . Por exemplo, se você deseja fornecer dois contêineres diferentes, mas semelhantes, como uma fila e uma fila de prioridade, deve usar a herança.
    • Agora você pode combinar os dois, você pode escrever uma classe base de modelo a partir da qual uma classe de modelo é derivada
  • Escalabilidade

    • As classes devem ser projetadas para serem extensíveis e podem ser estendidas derivando outras classes delas . Entretanto, uma classe bem projetada não deve ser modificada, ou seja, seu comportamento deve ser extensível sem modificar sua implementação . Isso é chamado de Princípio Aberto/Fechado (OCP)

5.3 Projetando interfaces úteis

5.3.1 Projete uma interface fácil de usar
  • usar a abordagem familiar

    • A melhor estratégia para desenvolver uma interface fácil de usar é seguir formas familiares e padronizadas de fazer as coisas . Quando as pessoas encontram uma interface semelhante a uma que usaram no passado, elas a entendem melhor, a adotam com mais facilidade e são menos propensas a usá-la incorretamente. A inovação é obviamente importante, mas a inovação deve estar na implementação subjacente, não na interface
    • Voltando ao C++, esta estratégia afirma que as interfaces desenvolvidas devem seguir padrões familiares aos programadores C++
    • C++ fornece um recurso de linguagem chamado sobrecarga de operador para ajudar a desenvolver interfaces fáceis de usar para objetos
  • Não omita as funções necessárias

    • Primeiro, a interface deve incluir todos os comportamentos que o usuário pode usar
    • Em segundo lugar, inclua o máximo de funcionalidade possível em sua implementação
  • Fornece uma interface limpa

    • Não forneça funções redundantes na interface, mantenha a interface concisa
  • Forneça documentação e comentários

    • Existem duas maneiras de fornecer documentação de interface: comentários dentro da própria interface e documentação externa . Ambos os tipos de documentação devem ser fornecidos sempre que possível. A maioria das APIs expostas fornece apenas documentação externa: faltam comentários em muitos arquivos de cabeçalho padrão do UNIX e do Windows. No UNIX, a documentação geralmente está na forma de manuais on-line chamados manpages. No Windows, os IDEs geralmente vêm com documentação
5.3.2 Projetar uma interface comum
  • Fornece vários métodos para executar a mesma função

    • Para manter todos os "clientes" satisfeitos, às vezes são oferecidas várias maneiras de executar a mesma função. No entanto, essa abordagem deve ser usada com cautela, pois muitos aplicativos podem sobrecarregar facilmente a interface
  • fornecer personalização

    • A personalização está disponível para maior flexibilidade de interface. A personalização pode ser tão simples quanto permitir que os usuários ativem ou desativem o registro de erros. A premissa básica da personalização é fornecer a cada cliente a mesma funcionalidade básica, mas dar ao usuário a capacidade de ajustá-la ligeiramente
    • Fornece maior personalização por meio de ponteiros de função e parâmetros de modelo
5.3.3 Coordenação de comunalidade e usabilidade
  • Fornece várias interfaces

    • Para reduzir a complexidade e fornecer funcionalidade suficiente, duas interfaces independentes são fornecidas. Isso é chamado de Princípio de Segregação de Interface (Princípio de Segregação de Interface, ISP). Por exemplo, uma biblioteca de rede de uso geral pode ser escrita com duas direções separadas: uma que fornece uma interface de rede para jogos e outra que fornece uma interface de rede para Hypertext Transfer Protocol (HTTP, um protocolo de navegação na web)
  • Torne as funções comuns fáceis de usar

    • Ao fornecer uma interface comum, algumas funções são usadas com mais frequência do que outras. Deve tornar os recursos comuns fáceis de usar, ao mesmo tempo em que oferece opções para recursos avançados

5.4 Princípios SÓLIDOS

insira a descrição da imagem aqui

Acho que você gosta

Origin blog.csdn.net/qq_42994487/article/details/131093651
Recomendado
Clasificación