Szukaj…


Ukrywanie szczegółów implementacji strategii

Bardzo powszechną wytyczną w projektowaniu obiektowym jest „jak najmniej, ale tyle, ile potrzeba”. Dotyczy to również wzorca strategii: Zazwyczaj wskazane jest ukrycie szczegółów implementacji, na przykład, które klasy faktycznie implementują strategie.

W przypadku prostych strategii, które nie zależą od parametrów zewnętrznych, najczęstszym podejściem jest uczynienie samej klasy implementującej prywatną (klasy zagnieżdżone) lub pakietową i ujawnienie instancji za pomocą statycznego pola klasy publicznej:

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
);

Pole publiczne Animal.AGE_COMPARATOR definiuje strategię, która może być następnie użyta w metodach takich jak Collections.sort , ale nie wymaga od użytkownika wiedzy na temat jej implementacji, nawet klasy implementującej.

Jeśli wolisz, możesz użyć anonimowej klasy:

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...
}

Jeśli strategia jest nieco bardziej złożona i wymaga parametrów, bardzo często stosuje się statyczne metody fabryczne, takie jak Collections.reverseOrder(Comparator<T>) . reverseOrder() typ metody nie powinien ujawniać żadnych szczegółów implementacji, np. reverseOrder() jest implementowany podobnie

public static <T> Comparator<T> reverseOrder(Comparator<T> cmp) {
  // (Irrelevant lines left out.)
  return new ReverseComparator2<>(cmp);
}

Przykład wzorca strategii w java z klasą Context

Strategia:

Strategy jest wzorem behawioralnym, który pozwala dynamicznie zmieniać algorytm z rodziny powiązanych algorytmów.

UML wzorca strategii z Wikipedii

wprowadź opis zdjęcia tutaj :

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);
    }
    
}

wynik:

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

Opis problemu: Zaoferuj 25% zniżki na cenę przedmiotu w miesiącach lipiec-grudzień. Nie udzielaj żadnych rabatów za miesiące styczeń-czerwiec.

Powyższy przykład pokazuje użycie wzorca Strategy z Context . Context może służyć jako pojedynczy punkt kontaktowy dla Client .

Dwie strategie - NoOfferStrategy i QuarterDiscountStrategy zostały zadeklarowane zgodnie z QuarterDiscountStrategy problemu.

Jak pokazano w kolumnie wyników, otrzymasz zniżkę w zależności od wprowadzonego miesiąca

Przypadki użycia dla wzorca strategii :

  1. Użyj tego wzorca, gdy masz rodzinę wymiennych algorytmów i musisz zmienić algorytm w czasie wykonywania.

  2. Zachowaj czystość kodu, usuwając instrukcje warunkowe

Wzór strategii bez klasy kontekstu / Java

Poniżej przedstawiono prosty przykład użycia wzorca strategii bez klasy kontekstu. Istnieją dwie strategie implementacji, które implementują interfejs i rozwiązują ten sam problem na różne sposoby. Użytkownicy klasy EnglishTranslation mogą wywołać metodę translate i wybrać strategię, której chcieliby użyć do tłumaczenia, określając pożądaną strategię.

// 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
    }
}

Korzystanie z interfejsów funkcjonalnych Java 8 w celu wdrożenia wzorca strategii

Celem tego przykładu jest pokazanie, w jaki sposób możemy zrealizować wzorzec strategii przy użyciu interfejsów funkcjonalnych Java 8. Zaczniemy od prostych kodów przypadków użycia w klasycznej Javie, a następnie przekodujemy go w sposób Java 8.

Przykładowy problem, którego używamy, to rodzina algorytmów (strategii), które opisują różne sposoby komunikacji na odległość.

Klasyczna wersja Java

Umowę dotyczącą naszej rodziny algorytmów określa następujący interfejs:

public interface CommunicateInterface {
    public String communicate(String destination);
}

Następnie możemy zaimplementować szereg algorytmów w następujący sposób:

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..";
    }
}

Można je utworzyć w następujący sposób:

CommunicateViaPhone communicateViaPhone = new CommunicateViaPhone();
CommunicateViaEmail communicateViaEmail = new CommunicateViaEmail();
CommunicateViaVideo communicateViaVideo = new CommunicateViaVideo();

Następnie wdrażamy usługę wykorzystującą strategię:

public class CommunicationService {
    private CommunicateInterface communcationMeans;

    public void setCommuncationMeans(CommunicateInterface communcationMeans) {
        this.communcationMeans = communcationMeans;
    }

    public void communicate(String destination) {
        this.communcationMeans.communicate(destination);
    }
}

Wreszcie możemy zastosować różne strategie w następujący sposób:

CommunicationService communicationService = new CommunicationService();

// via phone
communicationService.setCommuncationMeans(communicateViaPhone);
communicationService.communicate("1234567");

// via email
communicationService.setCommuncationMeans(communicateViaEmail);
communicationService.communicate("[email protected]");

Korzystanie z interfejsów funkcjonalnych Java 8

Kontrakt różnych implementacji algorytmów nie wymaga dedykowanego interfejsu. Zamiast tego możemy to opisać za pomocą istniejącego interfejsu java.util.function.Function<T, R> .

Różne algorytmy tworzące the family of algorithms można wyrazić jako wyrażenia lambda. Zastępuje to klasy strategii i ich instancje.

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..";

Następnie możemy zakodować „usługę” w następujący sposób:

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);
    }
}

Wreszcie stosujemy strategie w następujący sposób

CommunicationService communicationService = new CommunicationService();

// via phone
communicationService.setCommuncationMeans(communicateViaPhone);
communicationService.communicate("1234567");

// via email
communicationService.setCommuncationMeans(communicateViaEmail);
communicationService.communicate("[email protected]");

Lub nawet:

communicationService.setCommuncationMeans(
    destination -> "communicating " + destination + " via Smoke signals.." );
CommunicationService.communicate("anyone");

Strategia (PHP)

Przykład z 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;
    }
}


Modified text is an extract of the original Stack Overflow Documentation
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow