Design patterns
Шаблон стратегии
Поиск…
Краткое описание реализации стратегии
Очень общий ориентир в объектно-ориентированном дизайне «как можно меньше, но насколько это необходимо». Это также относится к шаблону стратегии: обычно желательно скрывать детали реализации, например, какие классы фактически реализуют стратегии.
Для простых стратегий, не зависящих от внешних параметров, наиболее распространенный подход заключается в том, чтобы сделать сам реализующий класс приватным (вложенные классы) или private-package и подвергать экземпляр через статическое поле открытого класса:
public class Animal {
private static class AgeComparator implements Comparator<Animal> {
public int compare(Animal a, Animal b) {
return a.age() - b.age();
}
}
// Note that this field has the generic type Comparator<Animal>, *not*
// Animal.AgeComparator!
public static final Comparator<Animal> AGE_COMPARATOR = new AgeComparator();
private final int age;
Animal(int age) {
this.age = age;
}
public int age() {
return age;
}
}
List<Animal> myList = new LinkedList<>();
myList.add(new Animal(10));
myList.add(new Animal(5));
myList.add(new Animal(7));
myList.add(new Animal(9));
Collections.sort(
myList,
Animal.AGE_COMPARATOR
);
Animal.AGE_COMPARATOR
поле Animal.AGE_COMPARATOR
определяет стратегию, которая затем может использоваться в таких методах, как Collections.sort
, но она не требует, чтобы пользователь знал что-либо о ее реализации, даже не о классе реализации.
Если вы предпочитаете, вы можете использовать анонимный класс:
public class Animal {
public static final Comparator<Animal> AGE_COMPARATOR = new Comparator<Animal> {
public int compare(Animal a, Animal b) {
return a.age() - b.age();
}
};
// other members...
}
Если стратегия немного сложнее и требует параметров, очень часто используют статические заводские методы, такие как Collections.reverseOrder(Comparator<T>)
. Возвращаемый тип метода не должен раскрывать какие-либо детали реализации, например, reverseOrder()
реализуется подобно
public static <T> Comparator<T> reverseOrder(Comparator<T> cmp) {
// (Irrelevant lines left out.)
return new ReverseComparator2<>(cmp);
}
Пример шаблона стратегии в Java с классом Context
Стратегия:
Strategy
- это поведенческая модель, которая позволяет динамически изменять алгоритм из семейства связанных алгоритмов.
Шаблон UML стратегии из Википедии
import java.util.*;
/* Interface for Strategy */
interface OfferStrategy {
public String getName();
public double getDiscountPercentage();
}
/* Concrete implementation of base Strategy */
class NoDiscountStrategy implements OfferStrategy{
public String getName(){
return this.getClass().getName();
}
public double getDiscountPercentage(){
return 0;
}
}
/* Concrete implementation of base Strategy */
class QuarterDiscountStrategy implements OfferStrategy{
public String getName(){
return this.getClass().getName();
}
public double getDiscountPercentage(){
return 0.25;
}
}
/* Context is optional. But if it is present, it acts as single point of contact
for client.
Multiple uses of Context
1. It can populate data to execute an operation of strategy
2. It can take independent decision on Strategy creation.
3. In absence of Context, client should be aware of concrete strategies. Context acts a wrapper and hides internals
4. Code re-factoring will become easy
*/
class StrategyContext {
double price; // price for some item or air ticket etc.
Map<String,OfferStrategy> strategyContext = new HashMap<String,OfferStrategy>();
StrategyContext(double price){
this.price= price;
strategyContext.put(NoDiscountStrategy.class.getName(),new NoDiscountStrategy());
strategyContext.put(QuarterDiscountStrategy.class.getName(),new QuarterDiscountStrategy());
}
public void applyStrategy(OfferStrategy strategy){
/*
Currently applyStrategy has simple implementation. You can Context for populating some more information,
which is required to call a particular operation
*/
System.out.println("Price before offer :"+price);
double finalPrice = price - (price*strategy.getDiscountPercentage());
System.out.println("Price after offer:"+finalPrice);
}
public OfferStrategy getStrategy(int monthNo){
/*
In absence of this Context method, client has to import relevant concrete Strategies everywhere.
Context acts as single point of contact for the Client to get relevant Strategy
*/
if ( monthNo < 6 ) {
return strategyContext.get(NoDiscountStrategy.class.getName());
}else{
return strategyContext.get(QuarterDiscountStrategy.class.getName());
}
}
}
public class StrategyDemo{
public static void main(String args[]){
StrategyContext context = new StrategyContext(100);
System.out.println("Enter month number between 1 and 12");
int month = Integer.parseInt(args[0]);
System.out.println("Month ="+month);
OfferStrategy strategy = context.getStrategy(month);
context.applyStrategy(strategy);
}
}
выход:
Enter month number between 1 and 12
Month =1
Price before offer :100.0
Price after offer:100.0
Enter month number between 1 and 12
Month =7
Price before offer :100.0
Price after offer:75.0
Заявление о проблемах: предложите скидку 25% на цену товара за июль-декабрь. Не предоставляйте скидку на месяцы с января по июнь.
В приведенном выше примере показано использование шаблона Strategy
с помощью Context
. Context
может использоваться как единая точка контакта для Client
.
Две стратегии - NoOfferStrategy
и QuarterDiscountStrategy
объявлены как QuarterDiscountStrategy
проблемы.
Как показано в столбце вывода, вы получите скидку в зависимости от введенного вами месяца
Использовать пример (ы) для шаблона стратегии :
Используйте этот шаблон, когда у вас есть семейство сменных алгоритмов, и вам нужно изменить алгоритм во время выполнения.
Держите код чище, удалив условные утверждения
Стратегия без контекстного класса / Java
Ниже приведен простой пример использования шаблона стратегии без класса контекста. Существуют две стратегии реализации, которые реализуют интерфейс и решают одну и ту же проблему по-разному. Пользователи класса EnglishTranslation могут вызывать метод перевода и выбирать, какую стратегию они хотели бы использовать для перевода, указав желаемую стратегию.
// The strategy interface
public interface TranslationStrategy {
String translate(String phrase);
}
// American strategy implementation
public class AmericanTranslationStrategy implements TranslationStrategy {
@Override
public String translate(String phrase) {
return phrase + ", bro";
}
}
// Australian strategy implementation
public class AustralianTranslationStrategy implements TranslationStrategy {
@Override
public String translate(String phrase) {
return phrase + ", mate";
}
}
// The main class which exposes a translate method
public class EnglishTranslation {
// translate a phrase using a given strategy
public static String translate(String phrase, TranslationStrategy strategy) {
return strategy.translate(phrase);
}
// example usage
public static void main(String[] args) {
// translate a phrase using the AustralianTranslationStrategy class
String aussieHello = translate("Hello", new AustralianTranslationStrategy());
// Hello, mate
// translate a phrase using the AmericanTranslationStrategy class
String usaHello = translate("Hello", new AmericanTranslationStrategy());
// Hello, bro
}
}
Использование функциональных интерфейсов Java 8 для реализации шаблона стратегии
Цель этого примера - показать, как мы можем реализовать шаблон стратегии с использованием функциональных интерфейсов Java 8. Мы начнем с простых кодов использования в классической Java, а затем перекодируем их на Java 8.
Пример проблемы, которую мы используем, - это семейство алгоритмов (стратегий), которые описывают разные способы общения на расстоянии.
Классическая версия Java
Контракт для нашего семейства алгоритмов определяется следующим интерфейсом:
public interface CommunicateInterface {
public String communicate(String destination);
}
Затем мы можем реализовать ряд алгоритмов, а именно:
public class CommunicateViaPhone implements CommunicateInterface {
@Override
public String communicate(String destination) {
return "communicating " + destination +" via Phone..";
}
}
public class CommunicateViaEmail implements CommunicateInterface {
@Override
public String communicate(String destination) {
return "communicating " + destination + " via Email..";
}
}
public class CommunicateViaVideo implements CommunicateInterface {
@Override
public String communicate(String destination) {
return "communicating " + destination + " via Video..";
}
}
Они могут быть созданы следующим образом:
CommunicateViaPhone communicateViaPhone = new CommunicateViaPhone();
CommunicateViaEmail communicateViaEmail = new CommunicateViaEmail();
CommunicateViaVideo communicateViaVideo = new CommunicateViaVideo();
Затем мы реализуем сервис, который использует стратегию:
public class CommunicationService {
private CommunicateInterface communcationMeans;
public void setCommuncationMeans(CommunicateInterface communcationMeans) {
this.communcationMeans = communcationMeans;
}
public void communicate(String destination) {
this.communcationMeans.communicate(destination);
}
}
Наконец, мы можем использовать различные стратегии следующим образом:
CommunicationService communicationService = new CommunicationService();
// via phone
communicationService.setCommuncationMeans(communicateViaPhone);
communicationService.communicate("1234567");
// via email
communicationService.setCommuncationMeans(communicateViaEmail);
communicationService.communicate("[email protected]");
Использование функциональных интерфейсов Java 8
Контракт реализации различных алгоритмов не требует специального интерфейса. Вместо этого мы можем описать это, используя существующий интерфейс java.util.function.Function<T, R>
.
Различные алгоритмы, составляющие the family of algorithms
могут быть выражены как лямбда-выражения. Это заменяет классы стратегий и их экземпляры.
Function<String, String> communicateViaEmail =
destination -> "communicating " + destination + " via Email..";
Function<String, String> communicateViaPhone =
destination -> "communicating " + destination + " via Phone..";
Function<String, String> communicateViaVideo =
destination -> "communicating " + destination + " via Video..";
Затем мы можем закодировать «сервис» следующим образом:
public class CommunicationService {
private Function<String, String> communcationMeans;
public void setCommuncationMeans(Function<String, String> communcationMeans) {
this.communcationMeans = communcationMeans;
}
public void communicate(String destination) {
this.communcationMeans.communicate(destination);
}
}
Наконец, мы используем стратегии следующим образом
CommunicationService communicationService = new CommunicationService();
// via phone
communicationService.setCommuncationMeans(communicateViaPhone);
communicationService.communicate("1234567");
// via email
communicationService.setCommuncationMeans(communicateViaEmail);
communicationService.communicate("[email protected]");
Или даже:
communicationService.setCommuncationMeans(
destination -> "communicating " + destination + " via Smoke signals.." );
CommunicationService.communicate("anyone");
Стратегия (PHP)
Пример из www.phptherightway.com
<?php
interface OutputInterface
{
public function load();
}
class SerializedArrayOutput implements OutputInterface
{
public function load()
{
return serialize($arrayOfData);
}
}
class JsonStringOutput implements OutputInterface
{
public function load()
{
return json_encode($arrayOfData);
}
}
class ArrayOutput implements OutputInterface
{
public function load()
{
return $arrayOfData;
}
}