Evitando armadilhas orientadas a objetos, migrando de C, o que funcionou para você?


12

Estou programando em linguagens procedurais há algum tempo, e minha primeira reação a um problema é começar a dividi-lo em tarefas a serem executadas, em vez de considerar as diferentes entidades (objetos) que existem e seus relacionamentos.

Eu tive um curso universitário em OOP e entendo os fundamentos de encapsulamento, abstração de dados, polimorfismo, modularidade e herança.

Eu li /programming/2688910/learning-to-think-in-the-object-oriented-way e /programming/1157847/learning-object-oriented-thinking , e analisarei alguns dos livros apontados nessas respostas.

Penso que vários dos meus projetos de médio e grande porte se beneficiarão do uso efetivo do OOP, mas como iniciante, gostaria de evitar erros comuns e demorados.

Com base em suas experiências, quais são essas armadilhas e quais são as formas razoáveis ​​de contorná-las? Se você pudesse explicar por que elas são armadilhas, e como sua sugestão é eficaz para resolver o problema, ela será apreciada.

Estou pensando na linha de algo como "É comum ter um número razoável de métodos de observação e modificador e usar variáveis ​​privadas ou existem técnicas para consolidá-las / reduzi-las?"

Não estou preocupado em usar o C ++ como uma linguagem OO pura, se houver boas razões para misturar métodos. (Remanescente dos motivos para usar GOTOs, embora com moderação.)

Obrigado!


2
não é digno de uma resposta completa, mas importante, que demorei muito tempo para aceitar (ou seja, eu li bastante sobre isso, mas o tratei como conversa de fanboy): prefira funções gratuitas do que funções-membro. Isso mantém suas aulas mínimas, o que é uma coisa boa.
21711 stjn

@stijn Essencialmente, você está dizendo que se não precisa estar na turma, não coloque lá. Por exemplo, vejo muitas funções de membro de utilidade que poderiam facilmente ser funções livres no código que li até agora.
Stephen

sim é isso. Se você procurar por 'preferir não-membro e não-amigo', encontrará muitas informações sobre ele. No final tudo se resume a aderir ao princípio único Responsabilidade
stijn

Respostas:


10

Uma coisa importante que aprendi é projetar classes de fora para dentro. Projete a interface antes mesmo de começar a pensar na implementação. Isso tornará a classe muito, muito mais intuitiva para seus usuários (aqueles que a utilizam) do que escrever os algoritmos subjacentes e criar a classe, além de escrever novas funções públicas de membros conforme necessário.


7
Além disso, podemos 'programar por intenção', ou seja, escrever algum código de exemplo que usaria a nova classe, ver quais tipos de métodos e políticas o tornam mais confortável de usar. Em seguida, use essas informações como linha de base para implementações.

2
Excelente em teoria - crie interfaces e você está basicamente pronto. Mas isso não funciona tão simples na prática. O design e a implementação da interface geralmente andam de mãos dadas em iterações consecutivas até que a interface final seja cristalizada. Naquele momento, é provável que você também tenha a implementação final.
Gene Bushuyev

9

Bem, a primeira é a armadilha de expor muita informação. O padrão deve ser private, não public.

Depois disso vem muitos getters / setters. Digamos que eu tenho um membro de dados. Eu realmente preciso desses dados em outra classe? Ok, faça um getter. Eu realmente, reaally necessário alterar esses dados durante a vida útil do objeto? Então faça um levantador.

A maioria dos programadores iniciantes tem o padrão de criar um getter / setter para cada membro de dados. Isso desorganiza as interfaces e geralmente são más escolhas de design.


2
Sim, é bastante popular entre aqueles que entenderam superficialmente o encapsulamento para tornar os dados privados e depois explodir o encapsulamento com getters e setters.
Gene Bushuyev

Java é a única linguagem popular que ainda precisa de métodos get / set. Como qualquer outro idioma suporta propriedades, não há diferença sintática entre um membro de dados público e uma propriedade. Mesmo em Java, não é pecado ter membros de dados públicos se você também controlar todos os usuários da classe.
kevin Cline

Os membros de dados públicos são mais difíceis de manter. Com getters / setters (ou propriedades), você pode manter a interface enquanto altera a representação interna dos dados, sem alterar nenhum código externo. Nos idiomas em que as propriedades são sintaticamente idênticas às variáveis ​​de membro do ponto de vista do consumidor, esse ponto não se aplica.
tdammers

Até agora, acho que estou vendo os membros privados "meio que" como variáveis ​​locais em uma função ... o resto do mundo não precisa saber nada sobre eles para o fn operar ... e se o fn mudanças, apenas a interface precisa ser consistente. Eu não costumo usar o getter / setter de spam, por isso posso estar no caminho certo; de fato, olhando para uma classe de buffer que escrevi, não há membros de dados públicos. Obrigado!
Stephen

2

Quando cruzei esse abismo, decidi pela seguinte abordagem:

0) Comecei devagar, com aplicativos procedimentais de tamanho pequeno / médio e nenhum item de missão crítica em ação.

1) um mapeamento simples da 1ª passagem de como eu escreveria o programa do zero no estilo OO - o mais importante para mim naquele momento, e isso é subjetivo - era descobrir todas as classes base. Meu objetivo era encapsular o máximo possível nas classes base. Métodos virtuais puros para todo o possível nas classes base.

2) O próximo passo foi criar as derivações.

3) a etapa final foi - no código processual original, separar as estruturas de dados do código obsever / modificador. Em seguida, use a ocultação de dados e mapeie todos os dados comuns nas classes base, e nas subclasses foram os dados que não eram comuns em todo o programa. E o mesmo tratamento para o código processador de observador / modificador - toda a lógica 'usada em todos os lugares' foi inserida nas classes base. E visualize / modifique a lógica que apenas atuou em um subconjunto dos dados foi para as classes derivadas.

Isso é subjetivo, mas é RÁPIDO, se você conhece bem o código processual e as estruturas de dados. Uma FALHA em uma revisão de código ocorre quando um pouco de dados ou lógica não aparece nas classes base, mas é usado em qualquer lugar.


2

Dê uma olhada em outros projetos bem-sucedidos que usam OOP para ter uma noção de bom estilo. Eu recomendo olhar para o Qt , um projeto que sempre admiro ao tomar minhas próprias decisões de design.


Sim! Antes de uma pessoa se tornar mestre de alguma coisa, ela aprende a imitar os grandes mestres que trabalharam antes dele. Estudar um bom código implementado por outras pessoas é uma boa maneira de aprender. Eu recomendaria olhar para o impulso, começando por projetos simples e aprofundando-se à medida que a compreensão de alguém melhora.
Gene Bushuyev

1

Dependendo de quem você fala, todos os dados no OOP devem ser privados ou protegidos e estar disponíveis apenas através de acessadores e mutadores. Em geral, acho que essa é uma boa prática, mas há ocasiões em que eu divergo dessa norma. Por exemplo, se você tem uma classe (em Java, digamos) cujo único objetivo é agrupar alguns dados em uma unidade lógica, faz sentido deixar os campos públicos. Se for uma cápsula imutável, apenas marque-a como final e inicialize-a no construtor. Isso reduz a classe (neste caso) a pouco mais que uma estrutura (na verdade, usando C ++, você deve chamar isso de uma estrutura. Funciona como uma classe, mas a visibilidade padrão é pública e sua intenção é mais clara), mas Acho que você descobrirá que usá-lo é muito mais confortável nesses casos.

Uma coisa que você definitivamente não quer fazer é ter um campo que deve ser verificado quanto à consistência no mutador e deixá-lo público.


3
Eu também sugeriria que, se você tem essas classes que contêm apenas dados públicos, declare-as como structs - isso não faz diferença semântica, mas esclarece a intenção e faz com que seu uso pareça mais natural, especialmente para programadores em C.

1
Sim, eu concordo aqui se você estiver em C ++ (que a pergunta mencionou), mas faço a maior parte do meu trabalho em Java e, infelizmente, não temos estrutura.

você precisa distinguir claramente entre classes agregadas (caso não frequente) e classes com funcionalidade (caso geral). Este último deve praticar o encapsulamento e acessar seus membros somente via interface pública, nenhum dado deve ser exposto ao público.
Gene Bushuyev

1

O livro Implementation Patterns, de Kent Beck, é uma excelente base para o uso de mecanismos orientados a objetos, e não ao mau uso.


1

Não estou preocupado em usar o C ++ como uma linguagem OO pura, se houver boas razões para misturar métodos. (Remanescente dos motivos para usar GOTOs, embora com moderação.)

Eu realmente não achava que tinha muito a oferecer a conversa até ver isso. Eu tenho que discordar do sentimento. OOP é apenas um dos paradigmas que podem e devem ser usados ​​em C ++. Francamente, na minha opinião, não é uma de suas características mais fortes.

Do ponto de vista do OO, acho que o C ++ fica um pouco aquém. A ideia de ter funções não virtuais, por exemplo, é um sinal contra isso a esse respeito. Eu tive argumentos com aqueles que discordam de mim, mas membros não virtuais simplesmente não se encaixam no paradigma no que me diz respeito. Polimorfismo é um componente chave para OO e classes com funções não virtuais não são polimórficas no sentido OO. Então, como uma linguagem OO, acho que o C ++ é realmente bastante fraco quando comparado a linguagens como Java ou Objective-C.

Programação genérica, por outro lado, o C ++ possui essa muito boa. Ouvi dizer que também existem linguagens melhores para isso, mas a combinação de objetos e funções genéricas é algo bastante poderoso e expressivo. Além disso, pode ser extremamente rápido, tanto no tempo de programação quanto no tempo de processamento. É realmente nessa área que eu acho que o C ++ brilha, embora seja certo que poderia ser melhor (suporte à linguagem de conceitos, por exemplo). Alguém que pensa que deveria seguir o paradigma OO e tratar os outros na ordem da declaração goto em níveis de imoralidade está realmente perdendo por não olhar para esse paradigma.

A capacidade de metaprogramação de modelos também é bastante impressionante. Confira a biblioteca Boost.Units, por exemplo. Esta biblioteca fornece suporte de tipo para quantidades dimensionais. Fiz uso extensivo dessa biblioteca na empresa de engenharia em que trabalho atualmente. Ele apenas fornece um feedback muito mais imediato para um aspecto do possível programador ou mesmo erro de especificação. É impossível compilar um programa que use uma fórmula em que os dois lados do operador '=' não sejam dimensionalmente equivalentes sem a conversão explícita. Pessoalmente, não tenho experiência com nenhum outro idioma em que isso seja possível e, certamente, não com um que também tenha o poder e a velocidade do C ++.

A metaprogramação é um paradigma puramente funcional.

Realmente, acho que você já está adotando o C ++ com alguns conceitos errados infelizes. Os outros paradigmas além do OO não devem ser evitados, devem ser ALAVANCADOS. Use o paradigma natural para o aspecto do problema em que você está trabalhando. Não force objetos sobre o que não é essencialmente um problema propenso a objetos. Para mim, o OO não é nem metade da história do C ++.


A palavra-chave virtual não fornece funcionalidade de classe virtual em C ++? No que diz respeito ao comentário, o que eu estava dirigindo era que não estava preocupado em quebrar regras empíricas, desde que entendesse o raciocínio. No entanto, tenho procurado evitar o imperativo / processual a favor da OO do que seria necessário. Obrigado.
Stephen

métodos virtuais não é um pré-requisito para o polimorfismo, é apenas uma maneira comum de fazer isso. de fato, todo o resto é igual, e a virtualização de um método enfraquece o encapsulamento, porque você está aumentando o tamanho da API da classe e dificulta a execução de liskov e assim por diante. torne algo virtual apenas se você precisar de uma costura, algum lugar para injetar um novo comportamento por herança (embora a herança também seja algo a ser cauteloso no POO). virtual por causa do virtual não fazer uma classe "mais OOP"
sara

1

Queria aceitar uma resposta para essa pergunta, mas não consegui decidir uma resposta para dar a marca de seleção. Como tal, votei os autores originais e criei isso como uma resposta resumida. Obrigado a todos que levaram alguns minutos, descobri que o insight que você forneceu me deu uma boa direção e um pouco de garantia de que eu não estava fora dos trilhos.

@nightcracker

Bem, a primeira é a armadilha de expor muita informação. O padrão deve ser privado, não público. Depois disso vem muitos getters / setters.

Eu senti que tinha observado esse problema em ação no passado. Seus comentários também me fizeram lembrar que, ao ocultar as variáveis ​​subjacentes e sua implementação, tenho a liberdade de alterar sua implementação sem destruir nada que dependa delas.

Dominic Gurto

Projete a interface antes mesmo de começar a pensar na implementação. O projeto e a implementação da interface Gene Bushuyev geralmente andam de mãos dadas em iterações consecutivas até que a interface final seja cristalizada.

Eu pensei que o comentário de Dominic era um ótimo ideal para aspirar, mas acho que o comentário de Gene realmente atinge a realidade da situação. Até agora, vi isso em ação ... e me sinto um pouco melhor por não ser incomum. Penso que, à medida que amadurecer como programador, vou me inclinar para projetos mais completos, mas agora ainda sofro com o salto e recebo algum código por escrito.

wantTheBest

Comecei devagar, com aplicativos procedimentais de tamanho pequeno / médio e nenhum material de missão crítica no trabalho. no código processual original, separe as estruturas de dados do código obsever / modificador

Isso faz muito sentido ... Gostei da ideia de manter as coisas funcionando, mas refatorar algumas das coisas não críticas com as classes.

jpm

Uma coisa que você definitivamente não quer fazer é ter um campo que deve ser verificado quanto à consistência no mutador e deixá-lo público

Eu sei há algum tempo que esse é um dos pontos fortes de encapsular os dados ... poder impor consistência e, por esse motivo, condições / intervalos / etc.

Crazy Eddie

Alguém que pensa que deveria seguir o paradigma OO e tratar os outros na ordem da declaração goto em níveis de imoralidade está realmente perdendo por não olhar para esse paradigma. A capacidade de metaprogramação de modelos também é bastante impressionante.

Originalmente, eu errei muito na resposta de Crazy Eddie, acho que porque não tinha lido alguns dos tópicos mencionados ... como metaprogramação. Eu acho que a grande mensagem no post da CE foi que o C ++ é uma mistura de recursos e estilos que cada um deve ser usado com seu melhor potencial ... incluindo o imperativo, se é o que faz sentido.

Então, novamente, obrigado a todos que responderam!


0

A maior armadilha é a crença de que OOP é uma bala de prata ou o "único paradigma perfeito".

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.