Skip to content

Instantly share code, notes, and snippets.

@danilobatistaqueiroz
Last active July 24, 2019 17:52
Show Gist options
  • Save danilobatistaqueiroz/861a03b63c10ce43af5af4d9f9fa1808 to your computer and use it in GitHub Desktop.
Save danilobatistaqueiroz/861a03b63c10ce43af5af4d9f9fa1808 to your computer and use it in GitHub Desktop.
Design Patterns

GOF

São 23 padrões de projetos catalogados por Erich Gamma, Jonh Vlissides, Ralph Johnson, Richard Helm.
Escritos no livro Design Patterns: Elements of Reusable Object-Oriented Software (1994)

Os padrões de projeto GoF descrevem como resolver problemas recorrentes de projeto em software orientado a objetos.
São problemas comuns que eles catalogaram e pesquisaram o que os desenvolvedores costumavam usar de melhores técnicas para solucioná-los.


Abstract Factory

Definição
"Cria famílias de objetos relacionados sem especificar suas classes concretas".

Exemplo de Uso
Um Abstract Factory isola o código do cliente da criação de objetos.
A fábrica pode determinar qual tipo será retornado a partir de um arquivo de configuração.
O cliente não precisa especificar o tipo, já que ele já foi especificado no arquivo de configuração.

Implementação
A fábrica precisa de um método para criar o objeto concreto, que na assinatura irá retornar uma interface.
Esse método construtor recebe como parâmetro algo que irá determinar qual tipo concreto será instanciado.

Um Resumo de Implementação

    class ButtonFactory {
        public static Button createButton(String appearance) {
            switch(appearance) {
                case "osx":
                    return OSXButton::new;
                case "win":
                    return WinButton::new;
                default:
                    throw new IllegalArgumentException("unknown " + appearance);
           }
        }
    }
    public class Principal {
        public static void main(final String[] arguments) {
        String randomAppearance = "osx";
        Button button = AbstractFactoryExample.createButton(randomAppearance);
        button.paint();
    }

Builder

Definição
O Builder constrói objetos complexos passo a passo, isolando o processo de criação do cliente.
É necesssário uma classe Builder, e uma Director.
Dentro da classe Builder, existem os métodos Set para configurar os parâmetros na construção.
Cada método Set retorna a própria instância do Builder, assim, é possível executar a construção em cascata.
Já o Director recebe uma instância de Builder por construtor, e terá um método de construção.
Assim para cada variação de construção de uma determinado objeto, é criado um BuilderDirector.

Lombok possuí uma anotação que implementa uma variação mais simples do Builder Pattern.

Exemplo resumido de código:

    interface ArquivoBuilder {
        Arquivo build();
        ArquivoBuilder setTipo(String formato);
        ArquivoBuilder setBibliteca(String biblioteca);
    }
    public class ArquivoBuildDirector {
        private ArquivoBuilder builder;
        public ArquivoBuildDirector(ArquivoBuilder builder) {
            this.builder = builder;
        }
        public Arquivo construct() {
            return builder.setTipo("PDF")
                          .setBiblioteca("org.apache.pdfbox")
                          .build();
        }
    }

Uma outra variação do Builder mais simples, e muito útil no dia-a-dia:

    Email email = new Email.EmailBuilder()
        .addRecipient("john@Doe.com")
        .setMainText("Check the builder pattern")
        .setGreeting("Hi John!")
        .setClosing("Regards")
        .setTitle("Builder pattern resources")
        .build();

Prototype

Com prototype é possível escolher qual objeto criar em runtime.
Uma boa vantagem do prototype é que ele evita o custo de criar um novo objeto com new, quando isso for proibitivamente caro para uma determinada aplicação.
No prototype é utilizado a clonagem do objeto.

Um exemplo bem conhecido de uso de "herança" via prototype na linguagem é o Javascript.

Um exemplo de implementação  
    public abstract class Prototype implements Cloneable {
        public Prototype clone() throws CloneNotSupportedException{
            return (Prototype) super.clone();
        }
    }
	
    public class ConcretePrototype1 extends Prototype {
        public Prototype clone() throws CloneNotSupportedException {
            return (ConcretePrototype1)super.clone();
        }
    }

Singleton

É muito utilizado em frameworks, hoje em dia não faz muito sentido procurar implementar em aplicações web, visto que os frameworks já trazem pronto e a prova de falhas.


Adapter

Intenção
Converte a interface de uma classe em outra interface que os clientes esperam.
O "adapter" permite que as classes trabalhem juntas, o que não poderia ocorrer devido a interfaces incompatíveis.
Envolve uma classe existente com uma nova interface.
Serve para utilizar um componente antigo em um novo sistema.

Cria-se uma classe adaptadora, que herda da classe antiga, e implementa a nova interface que o cliente espera.

Exemplo

class ClasseExistente {
    public void exibir() {...}
}
interface InterfaceNova {
    public void mostrar();
}
class Adaptador extends ClasseExistente implements InterfaceNova {
    public void mostrar() {
        exibir();
    }
}
class Cliente {
    public void chamarMostrar(){
        InterfaceNova tela = Fabrica.getTela(); //Retorna o Adaptador
    }
}

Bridge

O padrão Bridge é uma forma de aplicar o antigo conselho, "prefira composição sobre herança".
Torna-se útil quando você precisa subclassificar diversas vezes diferentes de maneiras ortogonais entre si.
Digamos que você deve implementar uma hierarquia de desenhos coloridos.
Você não vai fazer subclasses de Shape com Rectangle e Circle e depois subclasses de Rectangle com RedRectangle, BlueRectangle e GreenRectangle e depois o mesmo para Circle, RedCircle, BlueCircle,... não é?
Você preferiria dizer que cada Shape tem uma cor, e esse é o padrão Bridge.

1- você tem uma interface
2- algumas classes que implementam essa interface (is-a)
3- uma classe abstrata que recebe por parametro uma das classes (has-a, injeção)
4- uma classe concreta que herda da classe abstrata, que passa uma das classes por parametro.
5- ao usar essa classe concreta (passo 4), passa-se o uma das classes (do passo 2) por parâmetro.

Um exemplo em PHP:

interface DrawingAPI {
    function drawCircle();
}
class DrawingAPI1 implements DrawingAPI {
    public function drawCircle() {...}
}
class DrawingAPI2 implements DrawingAPI {
    public function drawCircle() {...}
}
abstract class Shape {
    protected $drawingAPI;
    public abstract function draw();
    public abstract function resizeByPercentage();
    protected function __construct(DrawingAPI $drawingAPI) {
        $this->drawingAPI = $drawingAPI;
    }
}
class CircleShape extends Shape {
    public function __construct(DrawingAPI $drawingAPI) {
        parent::__construct($drawingAPI);
    }
    public function draw() {
        $this->drawingAPI->drawCircle();
    }
    public function resizeByPercentage() {...}
}
class Tester {
    public static function main()  {
        $shapes = array(
            new CircleShape(new DrawingAPI1()),
            new CircleShape(new DrawingAPI2()),
        );
        foreach ($shapes as $shape) {
            $shape->resizeByPercentage(2.5);
            $shape->draw();
        }
    }
}
Tester::main();

Bridge usando Java

Suponha que você seja um fabricante de automóveis.
E você tem 5 modelos diferentes de carros.
E você faz em 5 cores diferentes.
Agora vá escrever classes para cada tipo de carro que você faz.
Imediatamente você vai perceber que você precisa de 5 * 5 = 25 classes !!!

E suponha que você introduza mais um modelo de carro. Agora você tem 30 classes !!!
Mais uma cor na mistura e você tem 36 classes para codificar !!!

O padrão Bridge quebra essas permutações em combinações.

Então, ao invés de 5 * 5 = 25 classes, você tem 5 + 5 = 10 classes.
Adicione outro modelo e você tem 6 + 5 = 11 classes.
Adicione outra cor e você tem 6 + 6 = 12 classes.

interface IColor
{
	public String getColor();
}
 
class RedColor implements IColor
{
	public String getColor()
    {
        return "of Red Color";
    }
}
 
class BlueColor implements IColor
{
	public String getColor()
    {
        return "of Blue Color";
    }
}
 
interface ICarModel
{
	public String WhatIsMyType();
}
 
class Model_A implements ICarModel
{
  private IColor myColor;
  public Model_A(IColor obj) {
    myColor = obj;
  }
  
  public String WhatIsMyType() {
      return "I am a Model_A " + myColor.getColor();
  }
}

class Model_B implements ICarModel
{
  IColor myColor;
  public Model_B(IColor obj) {
    myColor = obj;
  }
  public String WhatIsMyType() {
    return "I am a Model_B " + myColor.getColor();
  }
};
 

public class HelloWorld
{
  public static void main(String[] args)
  {
    IColor red = new RedColor();
    IColor blue = new BlueColor();
 
    ICarModel modelA = new Model_B(red);
    ICarModel modelB = new Model_A(blue);
 
    System.out.println("\n" + modelA.WhatIsMyType());
    System.out.println("\n" + modelB.WhatIsMyType() + "\n\n");
  }
}

O mesmo problema/solução utilizando enum em Java:

enum Color {  BLUE, RED, WHITE, BLACK }
interface ICarModel {
  public String WhatIsMyType();
}
class Model_A implements ICarModel {
  private Color myColor;
  public Model_A(Color obj) {
    myColor = obj;
  }
  public String WhatIsMyType() {
    return "I am a Model_A " + myColor;
  }
}
class Model_B implements ICarModel {
  Color myColor;
  public Model_B(Color obj) {
    myColor = obj;
  }
  public String WhatIsMyType() {
    return "I am a Model_B " + myColor;
  }
}
public class HelloWorld {
  public static void main(String[] args) {
    ICarModel modelA = new Model_B(Color.RED);
    ICarModel modelB = new Model_A(Color.BLUE);
    System.out.println("\n" + modelA.WhatIsMyType());
    System.out.println("\n" + modelB.WhatIsMyType() + "\n\n");
  }
}

Strategy

Esse padrão tem como habilidade:

  • Definir uma família de algoritmos;
  • Encapsular cada algoritmo como uma classe;
  • Permite que eles possam ser trocados entre si

Pensando num sistema, em que vamos adicionar uma nova funcionalidade:
"Cálculo de Impostos"
A princípio podemos pensar:
Isso é simples, vou criar uma classe, colocar um método que vou passar por parâmetro o imposto e o valor a ser calculado.
Como podemos ver, o cálculo está funcionando, porém observando um pouco mais o código, podemos fazer algumas observações:
Ele está passando como String o nome do imposto seguido pelo seu valor.
Estamos programando em uma linguagem OO(Orientada a Objetos), porém, o código está procedural.
Se amanhã surgir um novo imposto, vamos ter novos if's.

Para evitar o problema de novos if's e tornar o código mais fácil de dar manutenção.
No lugar, o que podemos fazer é definir um conjunto de classes para que possam ser alteradas em tempo de execução.
Podemos utilizar um ENUM com cada bloco de if sendo um valor do enum.

Exemplo:

interface CalculoStrategy {
    double calcularImposto(double valorVenda);
    static CalculoStrategy pis() {
        return valorVenda -> valorVenda * 0.2;
    }
    static CalculoStrategy cofins() {
        return valorVenda -> valorVenda * 0.3;
    }
}
class Produto {
    private CalculoStrategy calculo;
    public void exibeValorDoImposto(int valor, int quantidade) {
        System.out.println(this.calculo.calcularImposto(valor*quantidade));
    }
    public void setStrategy(CalculoStrategy calculo) {
        this.calculo = calculo;
    }
}
public class StrategyTeste {
  public static void main(String[] args) {
    Produto f = new Produto();
    f.setStrategy(CalculoStrategy.pis());
    f.exibeValorDoImposto(10, 5);
  }
}

Exemplo utilizando enum:

    interface CalculoStrategy {
        double calcularImposto(double valorVenda);
    }
    enum CalculoDeImposto implements CalculoStrategy {
        COFINS {
            public double calcularImposto(double valorVenda) {
                return (valorVenda * 0.01);
            }
        },
        PIS {
            public double calcularImposto(double valorVenda) {
                return (valorVenda * 0.02);
            }
        },
        ISS {
            public double calcularImposto(double valorVenda) {
                return (100 + valorVenda * 0.03);
            }
        };
     }
class Produto {
    private CalculoStrategy calculo;
    public void exibeValorDoImposto(int valor, int quantidade) {
        System.out.println(this.calculo.calcularImposto(valor*quantidade));
    }
    public void setStrategy(CalculoStrategy calculo) {
        this.calculo = calculo;
    }
}
public class StrategyTeste {
  public static void main(String[] args) {
    Produto f = new Produto();
    f.setStrategy(CalculoDeImposto.COFINS);
    f.exibeValorDoImposto(10, 5);
  }
}

Chain of Resposibility

Esse padrão tem como habilidade:

  • Delegar a tarefa para uma outra classe caso a mesma não tenha a condição de resolver determinada tarefa.

Pontos chave da implementação:

  • Montar uma lista de classes que pertençam à cadeia de responsabilidades para um conjunto de tarefas.
  • Cada classe pertencente à essa cadeia deve ter um campo de sucessor.
  • Também devem ter um método para processar a tarefa, nesse método deve avaliar se há condições, caso contrário delegar ao sucessor.

Padrão muito empregado em Middlewares, tais como ServletFilter em Java, Express em Nodejs, MvcCore para .Net


Template Method


Composite

Composite serve para criar estruturas aninhadas, em que você trata um conjunto de objetos como só.
Muito utilizado em componentes do tipo que lista uma estrutura em árvore (diretórios e arquivos e coisas similares).

Estrutura:
Uma interface que exponha um método.
Uma classe concreta Composite que implemente a interface.
Uma classe concreta Leaf que implemente a interface.
A classe Composite será um nó na estrutura, podendo ser uma folha também.
Na classe Composite a implementação do método terá um loop, e para cada item, uma chamada desse mesmo método no item.
A classe Composite terá uma lista de outras classes podendo serem composite ou leaf.

fontes:
http://ikeptwalking.com/bridge-design-pattern-explained/
https://stackoverflow.com/a/319757/935330
https://en.wikipedia.org/wiki/Bridge_pattern
https://medium.com/collabcode/strategy-padr%C3%B5es-de-projeto-em-java-43889a3afc5a

GOF aplicado na JDK:
https://stackoverflow.com/questions/1673841/examples-of-gof-design-patterns-in-javas-core-libraries

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment