Padrões de design para evitar [fechado]

105

Muitas pessoas parecem concordar que o padrão Singleton tem uma série de desvantagens e alguns até sugerem evitar o padrão completamente. Há uma excelente discussão aqui . Por favor, encaminhe quaisquer comentários sobre o padrão Singleton para essa pergunta.

Minha pergunta : Existem outros padrões de design que devem ser evitados ou usados ​​com muito cuidado?

Brian Rasmussen
fonte
Eu só tenho que notar sobre a grande lista de Design Antipatterns deviq.com/antipatterns
Desenvolvedor
@casperOne, por que você fechou a pergunta? A pergunta era legítima.
bleepzter

Respostas:

149

Os padrões são complexos

Todos os padrões de design devem ser usados ​​com cuidado. Na minha opinião, você deve refatorar os padrões quando houver uma razão válida para fazer isso, em vez de implementar um padrão imediatamente. O problema geral com o uso de padrões é que eles adicionam complexidade. O uso excessivo de padrões torna um determinado aplicativo ou sistema difícil de desenvolver e manter.

Na maioria das vezes, existe uma solução simples e você não precisa aplicar nenhum padrão específico. Uma boa regra prática é usar um padrão sempre que partes do código tendem a ser substituídas ou precisam ser alteradas com frequência e estar preparado para aceitar a advertência de código complexo ao usar um padrão.

Lembre-se de que seu objetivo deve ser a simplicidade e empregar um padrão se você vir uma necessidade prática de oferecer suporte a mudanças em seu código.

Princípios sobre padrões

Pode parecer discutível usar padrões se eles puderem levar a soluções complexas e com engenharia excessiva. No entanto, é muito mais interessante para um programador ler sobre técnicas e princípios de design que estabelecem a base para a maioria dos padrões. Na verdade, um dos meus livros favoritos sobre 'padrões de design' enfatiza isso reiterando quais princípios são aplicáveis ​​ao padrão em questão. Eles são simples o suficiente para serem úteis do que os padrões em termos de relevância. Alguns dos princípios são gerais o suficiente para abranger mais do que programação orientada a objetos (OOP), como o Princípio de Substituição de Liskov , contanto que você possa construir módulos de seu código.

Há uma infinidade de princípios de design, mas aqueles descritos no primeiro capítulo do livro GoF são bastante úteis para começar.

  • Programe para uma 'interface', não uma 'implementação'. (Gang of Four 1995: 18)
  • Dê preferência à 'composição do objeto' em vez da 'herança de classe'. (Gang of Four 1995: 20)

Deixe-os penetrar em você por um tempo. Deve-se notar que, quando GoF foi escrito, uma interface significa qualquer coisa que seja uma abstração (o que também significa superclasses), não deve ser confundida com a interface como um tipo em Java ou C #. O segundo princípio vem do uso excessivo observado de herança, que infelizmente ainda é comum hoje .

A partir daí você pode ler sobre os princípios SOLID que foram divulgados por Robert Cecil Martin (também conhecido como Tio Bob) . Scott Hanselman entrevistou o tio Bob em um podcast sobre estes princípios :

  • S Responsabilidade ingle Princípio
  • O Pen Princípio Fechado
  • L iskov Princípio de Substituição
  • I nterface Segregação Princípio
  • D ependency Princípio Inversão

Esses princípios são um bom começo para ler e discutir com seus colegas. Você pode descobrir que os princípios se entrelaçam uns com os outros e com outros processos, como separação de interesses e injeção de dependência . Depois de fazer TDD por um tempo, você também pode descobrir que esses princípios surgem naturalmente na prática, pois você precisa segui-los até certo ponto para criar testes de unidade isolados e repetíveis .

spoike
fonte
7
1 Muito boa resposta. Parece que todo programador (novato) hoje conhece seus padrões de projeto ou pelo menos sabe que eles existem. Mas muitos nunca ouviram falar, quanto mais aplicar, alguns dos princípios absolutamente essenciais como a responsabilidade única para gerenciar a complexidade de seu código.
eljenso
21

O que mais preocupou os próprios autores de Design Patterns foi o padrão "Visitante".

É um "mal necessário" - mas é freqüentemente usado demais e a necessidade dele geralmente revela uma falha mais fundamental em seu projeto.

Um nome alternativo para o padrão "Visitante" é "Multi-dispatch", porque o padrão Visitante é o que você acaba com quando deseja usar uma linguagem OO de despacho de tipo único para selecionar o código a ser usado com base no tipo de dois (ou mais) objetos diferentes.

O exemplo clássico é que você tem a interseção entre duas formas, mas há um caso ainda mais simples que costuma ser esquecido: comparar a igualdade de dois objetos heterogêneos.

De qualquer forma, muitas vezes você acaba com algo assim:

interface IShape
{
    double intersectWith(Triangle t);
    double intersectWith(Rectangle r);
    double intersectWith(Circle c);
}

O problema com isso é que você juntou todas as suas implementações de "IShape". Você deu a entender que sempre que desejar adicionar uma nova forma à hierarquia, você também precisará alterar todas as outras implementações de "Forma".

Às vezes, este é o design mínimo correto - mas pense bem. Seu projeto realmente exige que você despache em dois tipos? Você está disposto a escrever cada uma das explosões combinatórias de métodos múltiplos?

Freqüentemente, ao introduzir outro conceito, você pode reduzir o número de combinações que realmente terá que escrever:

interface IShape
{
    Area getArea();
}

class Area
{
    public double intersectWith(Area otherArea);
    ...
}

Claro, depende - às vezes você realmente precisa escrever código para lidar com todos esses casos diferentes - mas vale a pena fazer uma pausa e pensar antes de mergulhar e usar o Visitor. Isso pode lhe poupar muita dor mais tarde.

Paul Hollingsworth
fonte
2
Falando sobre visitantes, o tio Bob usa "o tempo todo" butunclebob.com/ArticleS.UncleBob.IuseVisitor
Spoike
3
@Paul Hollingsworth Você pode fornecer uma referência de onde diz que os autores de Design Patterns estão preocupados (e por que eles estão preocupados)?
m3th0dman
16

Singletons - uma classe que usa o singleton X tem uma dependência difícil de ver e de isolar para teste.

Eles são usados ​​com frequência porque são convenientes e fáceis de entender, mas podem realmente complicar os testes.

Veja Singletons são mentirosos patológicos .

orip
fonte
1
Eles também podem simplesmente realizar o teste, pois podem fornecer um único ponto para injetar um objeto Mock. É tudo uma questão de obter o equilíbrio certo.
Martin Brown
1
@Martin: É claro que é possível mudar um singelton para teste (se você não usar a implementação singelton padrão), mas como isso é mais fácil do que passar pela implementação de teste no construtor?
orip
14

Eu acredito que o padrão Template Method geralmente é um padrão muito perigoso.

  • Muitas vezes, ele usa sua hierarquia de herança "pelos motivos errados".
  • As classes básicas tendem a ficar repletas de todos os tipos de código não relacionado.
  • Isso força você a bloquear o design, geralmente bem no início do processo de desenvolvimento. (Bloqueio prematuro em muitos casos)
  • Mudar isso em um estágio posterior torna-se cada vez mais difícil.
Krosenvold
fonte
2
Eu acrescentaria que sempre que você usa o Template Method, provavelmente é melhor usar Strategy. O problema com TemplateMethod é que há reentrada entre a classe base e a classe derivada, que geralmente é excessivamente acoplada.
Paul Hollingsworth
5
@Paul: O método de template é ótimo quando usado corretamente, ou seja, quando as partes que variam precisam saber muito sobre as partes que não variam. Minha opinião é que a estratégia deve ser usada quando o código base apenas chama o código personalizado e o método do modelo deve ser usado quando o código personalizado precisa inerentemente saber sobre o código base.
dsimcha
sim dsimcha, eu concordo ... contanto que o designer da classe esteja ciente disso.
Paul Hollingsworth
9

Não acho que você deva evitar Design Patterns (DP), e não acho que você deva se forçar a usar DPs ao planejar sua arquitetura. Só devemos usar DPs quando eles surgirem naturalmente de nosso planejamento.

Se definirmos desde o início que queremos usar um determinado DP, muitas de nossas futuras decisões de projeto serão influenciadas por essa escolha, sem garantia de que o DP que escolhemos seja adequado às nossas necessidades.

Uma coisa que também não devemos fazer é tratar um DP como uma entidade imutável, devemos adaptar o padrão às nossas necessidades.

Então, resumindo, não acho que devemos evitar os PDs, devemos abraçá-los quando já estão tomando forma em nossa arquitetura.

Megacan
fonte
7

Acho que o Active Record é um padrão usado em demasia que incentiva a mistura da lógica de negócios com o código de persistência. Ele não faz um trabalho muito bom em ocultar a implementação de armazenamento da camada de modelo e vincula os modelos a um banco de dados. Existem muitas alternativas (descritas no PoEAA), como Table Data Gateway, Row Data Gateway e Data Mapper, que muitas vezes fornecem uma solução melhor e certamente ajudam a fornecer uma melhor abstração para armazenamento. Além disso, seu modelo não precisa ser armazenado em um banco de dados; Que tal armazená-los como XML ou acessá-los usando serviços da web? Seria muito fácil mudar o mecanismo de armazenamento de seus modelos?

Dito isso, o Active Record nem sempre é ruim e é perfeito para aplicativos mais simples onde as outras opções seriam exageradas.

Tim Wardle
fonte
1
É verdade, mas depende até certo ponto da implementação.
Mike Woodhouse
6

É simples ... evite Design Patterns que não sejam claros para você ou aqueles nos quais você não se sinta confortável .

Para citar alguns ...

existem alguns padrões pouco práticos , como por exemplo:

  • Interpreter
  • Flyweight

também existem alguns mais difíceis de entender , como por exemplo:

  • Abstract Factory - O padrão de fábrica totalmente abstrato com famílias de objetos criados não é tão fácil quanto parece
  • Bridge - Pode ficar muito abstrato, se abstração e implementação forem divididas em subárvores, mas é um padrão muito utilizável em alguns casos
  • Visitor - A compreensão do mecanismo de despacho duplo é realmente OBRIGATÓRIO

e existem alguns padrões que parecem terrivelmente simples , mas não são uma escolha tão clara devido a vários motivos relacionados ao seu princípio ou implementação:

  • Singleton - não é um padrão totalmente ruim, apenas muito usado (muitas vezes lá, onde não é adequado)
  • Observer - ótimo padrão ... apenas torna o código muito mais difícil de ler e depurar
  • Prototype - verifica o compilador de negociações quanto ao dinamismo (que pode ser bom ou ruim ... depende)
  • Chain of responsibility - muitas vezes apenas forçado / artificialmente empurrado para o design

Para aqueles "pouco práticos", deve-se realmente pensar antes de usá-los, porque geralmente há soluções mais elegantes em algum lugar.

Para os mais "difíceis de agarrar" ... são uma grande ajuda, quando são usados ​​em locais adequados e quando bem implementados ... mas são um pesadelo, quando usados ​​indevidamente.

Agora, o que vem a seguir ...

Marcel Toth
fonte
O padrão Flyweight é obrigatório a qualquer momento quando você usa um recurso, geralmente uma imagem, mais de uma vez. Não é um padrão, é uma solução.
Cengiz Kandemir
5

Espero não apanhar muito por isso. Christer Ericsson escreveu dois artigos ( um , dois ) sobre o tópico de padrões de projeto em seu blog de detecção de colisão em tempo real . Seu tom é bastante áspero, e talvez um pouco provocador, mas o homem sabe o que faz, então eu não descartaria isso como delírios de um lunático.

falstro
fonte
Leituras interessantes. Obrigado pelos links!
Bombe
3
Os idiotas produzem códigos ruins. Os idiotas com padrões produzem códigos piores do que os idiotas que nunca viram padrões? Eu não acho que eles façam. Para pessoas inteligentes, os padrões fornecem um vocabulário bem conhecido, o que facilita a troca de ideias. A solução: aprender padrões e lidar apenas com programadores inteligentes.
Martin Brown
Eu não acho que seja realmente possível para um verdadeiro idiota produzir códigos piores - não importa a ferramenta que esteja usando
1800 INFORMAÇÕES
1
Acho que seu exemplo com o teste da faculdade apenas prova que as pessoas que desprezam o domínio do problema e não desejam estudá-lo por mais do que algumas horas em um único fim de semana produzirão respostas incorretas ao tentar resolver os problemas.
scriptocalypse
5

Alguns dizem que o localizador de serviço é um antipadrão.

Arnis Lapsa
fonte
Também é importante observar que às vezes o localizador de serviço é necessário. Por exemplo, quando você não tem controle adequado da instanciação do objeto (por exemplo, atributos com parâmetros não constantes em C #). Mas também é possível usar o localizador de serviço COM injeção de ctor.
Sinestésico,
2

Eu acredito que o padrão do observador tem muito a responder, ele funciona em casos muito gerais, mas conforme os sistemas se tornam mais complexos, ele se torna um pesadelo, precisando de notificações OnBefore (), OnAfter () e, muitas vezes, postando tarefas assíncronas para evitar novas entrada. Uma solução muito melhor é desenvolver um sistema de análise automática de dependência que instrumenta todos os acessos a objetos (com barreiras de leitura) durante os cálculos e cria automaticamente uma borda em um gráfico de dependência.

Jesse Pepper
fonte
4
Entendi tudo em sua resposta até a palavra "A"
1800 INFORMAÇÕES
Pode ser necessário expandir ou vincular a essa análise automática de dependência da qual você fala. Também no .NET, delegados / eventos são usados ​​em vez do padrão do observador.
Spoike
3
@Spoike: delegados / eventos são uma implementação do padrão do observador
orip
1
Meu ressentimento pessoal contra o Observer é que ele pode criar vazamentos de memória em linguagens com coleta de lixo. Quando você terminar com um objeto, você precisa se lembrar que o objeto não será limpo.
Martin Brown
@orip: sim, é por isso que você usa delegados / eventos. ;)
Spoike,
2

Um complemento ao post de Spoike, Refatorar para padrões é uma boa leitura.

Adeel Ansari
fonte
Na verdade, criei um link para o catálogo do livro na Internet. :)
Spoike
Oh! Eu não me preocupei em pairar sobre ele. Na verdade, logo após terminar a pergunta, este livro veio à minha mente e então vi sua resposta. Eu não consegui me impedir de postar. :)
Adeel Ansari
0

Iterator é mais um padrão GoF a ser evitado ou, pelo menos, a ser usado apenas quando nenhuma das alternativas estiver disponível.

As alternativas são:

  1. para cada loop. Essa construção está presente na maioria das linguagens convencionais e pode ser usada para evitar iteradores na maioria dos casos.

  2. seletores à la LINQ ou jQuery. Eles devem ser usados ​​quando for-each não for apropriado, porque nem todos os objetos do container devem ser processados. Ao contrário dos iteradores, os seletores permitem manifestar em um lugar quais tipos de objetos devem ser processados.

Volodymyr Frolov
fonte
Eu concordo com os seletores. Foreach é um iterador, a maioria das linguagens OO fornece uma interface iterável que você implementa para permitir um foreach.
Neil Aitken
Em algumas linguagens, cada construção pode ser implementada por meio de iteradores, mas o conceito disso é, na verdade, mais de alto nível e mais próximo dos seletores. Ao usar for-each, o desenvolvedor declara explicitamente que todos os elementos do contêiner devem ser processados.
Volodymyr Frolov
Iteradores são um ótimo padrão. O antipadrão implementaria IEnumerable / IEnumerator sem um iterador. Acredito que o LINQ foi possível por meio do yielditerador. Eric White tem uma ótima discussão sobre isso em C # 3.0: blogs.msdn.com/b/ericwhite/archive/2006/10/04/… . Além disso, verifique a discussão de Jeremy Likness sobre corrotinas com iteradores: wintellect.com/CS/blogs/jlikness/archive/2010/03/23/… .
@Ryan Riley, os iteradores são objetos de baixo nível e, portanto, devem ser evitados em design e código de alto nível. Os detalhes da implementação de iteradores e diferentes tipos de seletores não importam aqui. Os seletores, ao contrário dos iteradores, permitem que o programador expresse explicitamente o que desejam processar e de forma que sejam de alto nível.
Volodymyr Frolov
Fwiw, a sintaxe F # alternativa semelhante ao LINQ é `List.map (fun x -> x.Value) xs`, que é quase tão longa quanto a compreensão da lista.