Design patterns
Strategimönster
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
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 :
Använd detta mönster när du har en familj av utbytbara algoritmer och du måste ändra algoritmen vid körning.
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;
}
}