Sök…


Dölja detaljer för strategiimplementering

En mycket vanlig riktlinje för objektorienterad design är "så lite som möjligt men så mycket som nödvändigt". Detta gäller också strategimönstret: Det rekommenderas vanligtvis att dölja implementeringsdetaljer, till exempel vilka klasser som faktiskt implementerar strategier.

För enkla strategier som inte är beroende av externa parametrar är det vanligaste tillvägagångssättet att göra implementeringsklassen själv privat (kapslade klasser) eller paket-privat och exponera en instans genom ett statiskt fält i en offentlig klass:

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

Det offentliga fältet Animal.AGE_COMPARATOR definierar en strategi som sedan kan användas i metoder som Collections.sort , men det kräver inte att användaren vet något om dess implementering, inte ens implementeringsklassen.

Om du föredrar kan du använda en anonym klass:

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

Om strategin är lite mer komplex och kräver parametrar är det mycket vanligt att använda statiska fabriksmetoder som Collections.reverseOrder(Comparator<T>) . reverseOrder() bör inte avslöja några implementeringsdetaljer, t.ex. reverseOrder() implementeras som

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

Exempel på strategimönster i java med kontextklass

Strategi:

Strategy är ett beteendemönster som gör det möjligt att ändra algoritmen dynamiskt från en familj av relaterade algoritmer.

UML för strategimönster från Wikipedia

ange bildbeskrivning här :

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

produktion:

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

Problemmeddelande: Erbjud 25% rabatt på produktens pris för månaderna juli-december. Ge ingen rabatt för månaderna januari-juni.

Ovanstående exempel visar användningen av Strategy med Context . Context kan användas som en enda kontaktpunkt för Client .

Två strategier - NoOfferStrategy och QuarterDiscountStrategy har deklarerats enligt ett problemuttag.

Som visas i utmatningskolumnen får du rabatt beroende på vilken månad du har angett

Använd fall (er) för strategimönster :

  1. Använd detta mönster när du har en familj av utbytbara algoritmer och du måste ändra algoritmen vid körning.

  2. Håll koden renare genom att ta bort villkorade uttalanden

Strategimönster utan sammanhangsklass / Java

Följande är ett enkelt exempel på att använda strategimönstret utan en sammanhangsklass. Det finns två implementeringsstrategier som implementerar gränssnittet och löser samma problem på olika sätt. Användare av klassen EnglishTranslation kan ringa översättningsmetoden och välja vilken strategi de vill använda för översättningen genom att ange önskad 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
    }
}

Använda Java 8-funktionella gränssnitt för att implementera strategimönstret

Syftet med detta exempel är att visa hur vi kan förverkliga strategimönstret med funktionella Java 8-gränssnitt. Vi kommer att börja med enkla fallkoder i klassisk Java och sedan koda det på Java 8-sättet.

Exempelproblemet vi använder är en familj av algoritmer (strategier) som beskriver olika sätt att kommunicera på distans.

Den klassiska Java-versionen

Kontraktet för vår algoritmfamilj definieras av följande gränssnitt:

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

Sedan kan vi implementera ett antal algoritmer enligt följande:

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

Dessa kan inställas på följande sätt:

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

Därefter implementerar vi en tjänst som använder strategin:

public class CommunicationService {
    private CommunicateInterface communcationMeans;

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

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

Slutligen kan vi använda de olika strategierna enligt följande:

CommunicationService communicationService = new CommunicationService();

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

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

Använda Java 8-funktionella gränssnitt

Kontraktet för de olika algoritm-implementeringarna behöver inte ett särskilt gränssnitt. Istället kan vi beskriva det med det befintliga java.util.function.Function<T, R> .

De olika algoritmer som sammansätter the family of algorithms kan uttryckas som lambda-uttryck. Detta ersätter strategiklasserna och deras inställningar.

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

Därefter kan vi koda "tjänsten" enligt följande:

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

Slutligen använder vi strategierna enligt följande

CommunicationService communicationService = new CommunicationService();

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

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

Eller ens:

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

Strategi (PHP)

Exempel från 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
Licensierat under CC BY-SA 3.0
Inte anslutet till Stack Overflow