Design Pattern – Strategy

Olá, tudo bem?

Vou aproveitar que estou estudando sobre design patterns e colocar aqui alguns padrões encontrados atualmente. O livro base é o Design Patterns – Head First. Vou descrever aqui os mesmos padrões encontrados no livro, mas estarei utilizando exemplos diferentes e acrescendo mais material na medida do possível.
Esse estudo para patterns serve para a certificação OCJD. Essa certificação exige a entrega de um sistema onde você deverá explicar o porquê de cada escolha sobre o seu design.

Vamos utilizar o seguinte caso de uso: “Um site de carros de brinquedos está recebendo bastantes visitas e se tornando famoso. Mas seus investidores querem mais, querem que agora seja possível fazer simulações das ações dos carros de brinquedo na web.”

Atualmente nosso modelo se divide da seguinte forma (existem várias outras subclasses de ToyCar que não foram descritas na imagem abaixo):

Design Pattern - Strategy

Agora os investidores desejam que todos os carros de brinquedo exerçam a função de ligar o motor, para que os clientes possam ouvir seu ronco. Para isso, a solução mais “simples, rápida e prática” teoricamente seria aplicar uma herança. Que mal há em utilizar esse ótimo artifício fornecido pelo Java? Basta criar o método ligar motor (“startEngine()”) em nosso modelo e tudo estará resolvido.

Design Pattern - Strategy

Após a entrega da nova funcionalidade, a equipe de desenvolvimento vibrava com o bom funcionamento ao ouvirem o ronco dos motores dos carros de brinquedo. MAS, uma ligação é recebida e um pequeno stress começa a aparecer. Um dos investidores ao navegar pela aplicação encontra algo que nunca deveria acontecer, um carro de madeira ligando motor. Por que isso aconteceu? Vejamos algo que passou despercebido:

Design Pattern - Strategy

Nosso carro de madeira, que também tem como superclasse a classe ToyCar, acabou por herdar essa função que não cabia a ele. O que fazer para resolver esse problema? Poderíamos sobrescrever o método herdado:

    @Override
    public boolean startEngine() {
        // Do nothing
    }

Seria essa a melhor saída? Supondo que temos uma classe para um carro de metal (“MetalCar”) também iríamos ter que sobrescrever esse método. Cada hora nosso projeto fica mais complexo, pois à medida que criarmos mais modelos de carros que não precisam ter o motor ligado, a sobrescrita do método de ligar o motor será obrigatória.

Qual outra solução seria possível aqui? Vamos aplicar um primeiro princípio a ser tratado nessa série de artigos:
Identifique tudo o que varia e separe do restante que não irá variar.”
O que isso significa? Veja que o comportamento de ligar motor pode ou não aparecer para determinadas classes, com isso poderíamos separar esse comportamento da nossa classe ToyCar. Ao invés de deixar nossa classe ToyCar controlando esse comportamento por que não delegamos essa função? Hora de ver, um segundo princípio de padrão de projeto:
Programe para interface.”
Esse conceito nada mais é do que: utilizar todo o benefício de herança. Imagine que se tivermos uma interface com um método conhecido, não iria fazer qualquer diferença para nossa classe qual a implementação de motor estiver sendo relacionada a ele. Vamos criar uma Interface para tratar o comportamento do motor?

Design Pattern - Strategy

E como ficariam nossas classes? Primeiramente a classe ToyCar, depois a classe Mercedes e por último, nossa classe carro de Madeira:

// Interface and Engine Behavior implementations

public interface EngineBehavior {
    public boolean startEngine();
}

public class NoEngineBehavior implements EngineBehavior {

    @Override
    public boolean startEngine() {
        System.out.println("No engine to turn on");
        return true;
    }
}

public class RegularEngineBehavior implements EngineBehavior {

    @Override
    public boolean startEngine() {
        System.out.println("Engine On");
        return true;
    }
}

public class PowerfulEngineBehavior implements EngineBehavior {
    
    @Override
    public boolean startEngine() {
        System.out.println("Powerfull Engine On");
        return true;
    }
}

// Our Classes Implmentatioins
public class ToyCar {
    private int doorTotal;
    private int maxSpeed;
    protected EngineBehavior engineBehavior;
    // Others attributes
    
    public ToyCar(){
        engineBehavior = new RegularEngine();
    }
    
    public void startEngine(){
        engineBehavior.startEngine();
    }
    
    // Others methods
}

public class Mercedes extends ToyCar {
    
    public Mercedes(){
        engineBehavior = new PowerfulEngineBehavior();
    }
    
    @Override
    public void startEngine(){
        engineBehavior.startEngine();
    }    
}

public class WoodCar extends ToyCar {
    
    public WoodCar(){
        engineBehavior = new NoEngineBehavior();
    }
    
    @Override
    public void startEngine(){
        engineBehavior.startEngine();
    }
}

Claro que poderíamos alterar o modo em que a Interface está recebendo sua classe implementada (“engineBehavior = new NoEngineBehavior();”) e outras questões relacionadas a patterns, mas isso será assunto para próximos artigos.

Talvez você possa pensar: “Espera aí, dá no mesmo se eu comentar o código “startEngine()” em todas as classes ou colocar o código engineBehavior = new NoEngineBehavior(); pois terei essa “repetição” em toda classe.” Pense comigo, e se ao chamar o método “startEngine()” para um tipo de ToyCar que não tenha motor, fosse necessário uma ação como registrar no log essa tentativa? Do modo como nossas classes estão implementadas, bastaria alterar o método da classe NoEngineBehavior e pronto. Todas nossas classes de carros que não tem motor já estarão com esse comportamento ativo.

Três considerações finais:

  • O nome de uma classe deve indicar um substantivo. Coisas em geral: “Carro”, “Casa”, “Computador”. Em nosso caso o comportamento (behavior) virou um substantivo. Ele é uma classe que determina um padrão de comportamento.
  • Programar para Interface: Não significa necessariamente criar sempre uma interface (“Interface”). Significa programar para um super tipo, ou seja, você poderia estar usando uma Classe normal, Classe Abstrata ou Interface. Você tem que adaptar sua necessidade à melhor opção possível. No livro do Use a Cabeça (Head First), os autores deixam isso claro. Não confundir esse conceito com sempre criar uma interface para qualquer situação, mas sim um super tipo que venha a solucionar o seu problema.
  • Acabamos por ver mais um princípio de projeto:
    "Dar prioridade à composição."

Como vimos no exemplo acima, colocamos nossa interface como composição, isso facilitou e muito nosso trabalho.
Padrão de Projeto: Strategy
Princípios vistos:

  • Identifique tudo o que varia e separe do restante que não irá variar.
  • Programe para interface.
  • Dar prioridade à composição.

Espero que esse post possa te ajudar. Qualquer dúvida ou colocação basta postar abaixo.

Até a próxima.
Revisores: João Luiz Silva Silvestre / Diego Prado

19 thoughts on “Design Pattern – Strategy

  1. Parabens pelo post, já conhecia esse padrão mas é sempre ler por perspectivas diferentes, e padrões de projetos são sempre bem vindos, ainda compro o head first para a minha coleção.

  2. Olá Hebert, está de parabéns o post. Continue postando sobre design patterns pois é um tema interessante e sempre há diversas interpretações para o mesmo assunto.

    Agora vai uma dúvida:

    Na classe ToyCar qual o proposito da declaração do construtor de forma explicita?

    public ToyCar(){
    engineBehavior = new RegularEngine();
    }

    Por suposição acredito que na criação do objeto no construtor de ToyCar( ) em new RegularEngine( ); você esteja se referindo a classe RegularEngineBehavior( ); que assume previamente que o motor do carro estará ligado.

    /* :) to estudando para certificação e fiquei paranoico procurando a classe RegEng.xD */

    Agora o que me deixou pensativo…
    Não seria melhor ter um construtor padrão na classe ToyCar( ); ? posso fazer isto?

    Por favor, me corrija se eu esteja falando bobeira pois respeito muito você e seu trabalho, principalmente com as contribuições que faz com a comunidade.

    Quando um objeto que faz parte da arvore de herança com a classe ToyCar( ) é instanciado,ele executa uma chamada implícita em seu construtor a super(); que por sua vez irá percorrer a arvore da herança até a classe Object executando o constructor de ToyCar(); e de todas as outras classes relacionadas, que por sua vez define aquele objeto que faz herança a ToyCar( ) como um RegularEngineBehavior( ) mesmo que seja provisoriamente. Sim ou Não?

    Assim quando visualizamos:

    public class WoodCar extends ToyCar {

    public WoodCar(){
    engineBehavior = new NoEngineBehavior();
    }

    podemos dizer que em algum momento na execução este WoodCar( ) foi um RegularEngineBehavior( ) devido ao construtor da classe ToyCar( );?

    Aqui não vejo muitos problemas pois os construtores não realizam nenhum comportamento de alto acoplamento com a classe que o declara. Agora se eu não tivesse um construtor explicito na classe ToyCar( ) e declara-se diretamente no construtor das classes filhas qual o tipo do objeto, não ficaria mais coeso?

    ABRAÇO
    E Parabéns pelo post.

    • Olá Maurício, boa tarde.

      Concordo com você que ao fazer new WoodCar primeiramente o engine seria um RegularEngine.

      Mas pense também que caso você tenha que fazer new Fusca() você não precisaria sobrescrever o construtor da classe ToyCar.

      Existem diversas soluções possíveis e precisamos sempre escolher a melhor. Nesse caso, se chamar NoEngineBehavior estivesse degradando a memória, uma outra solução deveria ser utilizada.

      Quando levantamos esse tipo de questionamento é por que nossa mente está pensando em melhores soluções e padrões para o projeto. [=

      Obrigado pela presença.

Leave a Comment