JavaRush /Blogue Java /Random-PT /“Estratégia” de padrão de design

“Estratégia” de padrão de design

Publicado no grupo Random-PT
Olá! Em palestras anteriores, já encontramos um conceito como “padrão de design”. Caso você tenha esquecido, lembre-se: este termo denota uma certa solução padrão para um problema comum de programação. Padrão de design “Estratégia” - 1No JavaRush costumamos dizer que a resposta para quase todas as perguntas pode ser pesquisada no Google. Portanto, provavelmente alguém já resolveu com sucesso um problema semelhante ao seu. Portanto, os padrões são soluções testadas pelo tempo e pela prática para os problemas ou métodos mais comuns para resolver situações problemáticas. Essas são as mesmas “bicicletas” que em nenhum caso você precisa inventar sozinho, mas precisa saber como e quando aplicá-las :) Outra tarefa dos padrões é trazer a arquitetura para um único padrão. Ler o código de outra pessoa não é uma tarefa fácil! Cada um escreve de forma diferente, porque o mesmo problema pode ser resolvido de várias maneiras. Mas o uso de padrões permite que diferentes programadores entendam a lógica do programa sem se aprofundar em cada linha do código (mesmo que o vejam pela primeira vez!) Hoje veremos um dos padrões mais comuns chamado “Estratégia”. Padrão de design “Estratégia” - 2Vamos imaginar que estamos escrevendo um programa que funciona ativamente com o objeto Car. Neste caso, nem é particularmente importante o que exatamente o nosso programa faz. Para fazer isso, criamos um sistema de herança com uma classe pai Autoe três classes filhas: Sedan, Trucke F1Car.
public class Auto {

   public void gas() {
       System.out.println("Едем вперед");
   }

   public void stop() {

       System.out.println("Тормозим!");
   }
}

public class Sedan extends Auto {
}

public class Truck extends Auto {
}

public class F1Car extends Auto {
}
Todas as três classes filhas herdam dois métodos padrão do pai - gas()e stop() nosso programa é completamente simples: os carros só podem avançar e frear. Continuando nosso trabalho, decidimos adicionar um novo método aos carros - fill()(reabastecer). Vamos adicioná-lo à classe pai Auto:
public class Auto {

   public void gas() {
       System.out.println("Едем вперед");
   }

   public void stop() {

       System.out.println("Тормозим!");
   }

   public void fill() {
       System.out.println("Заправить бензин!");
   }
}
Parece que poderiam surgir problemas em uma situação tão simples? Bem, na verdade, eles já surgiram... “Estratégia” do Padrão de Design - 3
public class ChildrenBuggies extends Auto {

   public void fill() {

       //хм... Это детский багги, его не надо заправлять :/
   }
}
Surgiu em nosso programa um carro que não se enquadra no conceito geral - um carrinho infantil. Pode ser movido a pedal ou controlado por rádio, mas uma coisa é certa: não há onde colocar gasolina nele. Nosso esquema de herança resultou na distribuição de métodos comuns até mesmo para classes que não precisam deles. O que devemos fazer em tal situação? Bem, por exemplo, você pode substituir o método fill()na classe ChildrenBuggiespara que, ao tentar reabastecer o buggy, nada aconteça:
public class ChildrenBuggies extends Auto {

   @Override
   public void fill() {
       System.out.println("Игрушечную машину нельзя заправить!");
   }
}
Mas esta solução dificilmente pode ser considerada bem-sucedida, pelo menos por causa da duplicação de código. Por exemplo, a maioria das classes usará um método da classe pai, mas outras classes serão forçadas a substituí-lo. Se tivermos 15 classes e em 5-6 formos forçados a substituir o comportamento, a duplicação de código se tornará bastante extensa. Talvez as interfaces possam nos ajudar? Por exemplo, este:
public interface Fillable {

   public void fill();
}
Criaremos uma interface Fillablecom um método fill(). Conseqüentemente, os carros que precisam ser reabastecidos implementarão essa interface, mas outros carros (por exemplo, nosso buggy) não. Mas esta opção também não nos convém. Nossa hierarquia de classes poderá crescer para um número muito grande no futuro (imagine quantos tipos diferentes de carros existem no mundo). Abandonamos a opção de herança anterior porque não queríamos substituir o fill(). Aqui teremos que implementá-lo em todas as aulas! E se tivermos 50 deles? E se mudanças frequentes forem feitas em nosso programa (e em programas reais isso quase sempre acontece!), teremos que correr com a língua para fora entre todas as 50 classes e alterar o comportamento de cada uma delas manualmente. Então, o que devemos fazer no final? Para resolver nosso problema, vamos escolher um caminho diferente. Ou seja, vamos separar o comportamento da nossa classe da própria classe. O que isso significa? Como você sabe, qualquer objeto possui um estado (um conjunto de dados) e um comportamento (um conjunto de métodos). O comportamento da nossa classe de máquina consiste em três métodos gas()- stop()e fill(). Os dois primeiros métodos estão bem. Mas moveremos o terceiro método para fora da classe Auto. Esta será a separação do comportamento da classe (mais precisamente, separamos apenas parte do comportamento - os dois primeiros métodos permanecem em vigor). Para onde devemos mover nosso método fill()? Nada vem imediatamente à mente:/ Ele parecia estar completamente em seu lugar. Iremos movê-lo para uma interface separada - FillStrategy!
public interface FillStrategy {

   public void fill();
}
Por que precisamos dessa interface? É simples. Agora podemos criar diversas classes que irão implementar esta interface:
public class HybridFillStrategy implements FillStrategy {

   @Override
   public void fill() {
       System.out.println("Заправляем бензином or электричеством на выбор!");
   }
}

public class F1PitstopStrategy implements FillStrategy {

   @Override
   public void fill() {
       System.out.println("Заправляем бензин только после всех остальных procedures пит-стопа!");
   }
}

public class StandartFillStrategy implements FillStrategy {
   @Override
   public void fill() {
       System.out.println("Просто заправляем бензин!");
   }
}
Criamos três estratégias de comportamento – para carros convencionais, para híbridos e para carros de Fórmula 1. Cada estratégia implementa um algoritmo de reabastecimento separado. No nosso caso, isso é apenas uma saída para o console, mas pode haver alguma lógica complexa dentro do método. O que devemos fazer com isso a seguir?
public class Auto {

   FillStrategy fillStrategy;

   public void fill() {
       fillStrategy.fill();
   }

   public void gas() {
       System.out.println("Едем вперед");
   }

   public void stop() {
       System.out.println("Тормозим!");
   }

}
Usamos nossa interface FillStrategycomo um campo na classe pai Auto. Observação: não especificamos uma implementação específica, mas sim usamos a interface. E precisaremos de implementações específicas da interface FillStrategynas classes de carros filhos:
public class F1Car extends Auto {

   public F1Car() {
       this.fillStrategy = new F1PitstopStrategy();
   }
}

public class HybridAuto extends Auto {

   public HybridAuto() {
       this.fillStrategy = new HybridFillStrategy();
   }
}

public class Sedan extends Auto {

   public Sedan() {
       this.fillStrategy = new StandartFillStrategy();
   }
}
Vamos ver o que temos:
public class Main {

   public static void main(String[] args) {

       Auto sedan = new Sedan();
       Auto hybrid = new HybridAuto();
       Auto f1car = new F1Car();

       sedan.fill();
       hybrid.fill();
       f1car.fill();
   }
}
Saída do console:

Просто заправляем бензин!
Заправляем бензином or электричеством на выбор!
Заправляем бензин только после всех остальных procedures пит-стопа!
Ótimo, o processo de reabastecimento funciona como deveria! Aliás, nada nos impede de usar a estratégia como parâmetro no construtor! Por exemplo, assim:
public class Auto {

   private FillStrategy fillStrategy;

   public Auto(FillStrategy fillStrategy) {
       this.fillStrategy = fillStrategy;
   }

   public void fill() {
       this.fillStrategy.fill();
   }

   public void gas() {
       System.out.println("Едем вперед");
   }

   public void stop() {
       System.out.println("Тормозим!");
   }
}

public class Sedan extends Auto {

   public Sedan() {
       super(new StandartFillStrategy());
   }
}



public class HybridAuto extends Auto {

   public HybridAuto() {
       super(new HybridFillStrategy());
   }
}

public class F1Car extends Auto {

   public F1Car() {
       super(new F1PitstopStrategy());
   }
}
Vamos executar nosso método main()(ele permanece inalterado) e obter o mesmo resultado! Saída do console:

Просто заправляем бензин!
Заправляем бензином or электричеством на выбор!
Заправляем бензин только после всех остальных procedures пит-стопа!
O padrão Strategy define uma família de algoritmos, encapsula cada um deles e garante que sejam intercambiáveis. Ele permite modificar algoritmos independentemente de seu uso no lado do cliente (esta definição foi retirada do livro “Exploring Design Patterns” e me parece extremamente bem-sucedida). “Estratégia” do Padrão de Design - 4Isolamos a família de algoritmos que nos interessam (tipos de carros de reabastecimento) em interfaces separadas com diversas implementações. Nós os separamos da própria essência do carro. Portanto, agora, se precisarmos fazer alguma alteração neste ou naquele processo de reabastecimento, isso não afetará de forma alguma nossas classes de carros. Quanto à intercambialidade, para isso precisamos apenas adicionar um método setter à nossa classe Auto:
public class Auto {

   FillStrategy fillStrategy;

   public void fill() {
       fillStrategy.fill();
   }

   public void gas() {
       System.out.println("Едем вперед");
   }

   public void stop() {
       System.out.println("Тормозим!");
   }

   public void setFillStrategy(FillStrategy fillStrategy) {
       this.fillStrategy = fillStrategy;
   }
}
Agora podemos mudar estratégias rapidamente:
public class Main {

   public static void main(String[] args) {

       ChildrenBuggies buggies = new ChildrenBuggies();
       buggies.setFillStrategy(new StandartFillStrategy());

       buggies.fill();
   }
}
Se de repente os carrinhos infantis começarem a ser abastecidos com gasolina, nosso programa estará pronto para tal cenário :) Na verdade, é tudo! Você aprendeu outro padrão de design, que sem dúvida precisará e que o ajudará mais de uma vez ao trabalhar em projetos reais :) Até mais!
Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION