Design patterns
Strategie patroon
Zoeken…
Details van implementatie van strategie verbergen
Een veel voorkomende richtlijn bij objectgeoriënteerd ontwerp is "zo min mogelijk maar zo veel als nodig". Dit is ook van toepassing op het strategiepatroon: meestal is het raadzaam om implementatiedetails te verbergen, bijvoorbeeld welke klassen daadwerkelijk strategieën implementeren.
Voor eenvoudige strategieën die niet afhankelijk zijn van externe parameters, is de meest gebruikelijke aanpak om de implementatieklasse zelf privé te maken (geneste klassen) of pakket-privé en een instantie bloot te leggen via een statisch veld van een openbare klasse:
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
);
Het openbare veld Animal.AGE_COMPARATOR
definieert een strategie die vervolgens kan worden gebruikt in methoden zoals Collections.sort
, maar de gebruiker hoeft niets te weten over de implementatie, zelfs niet de implementatieklasse.
Als je wilt, kun je een anonieme klasse gebruiken:
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...
}
Als de strategie een beetje ingewikkelder is en parameters vereist, is het heel gebruikelijk om statische fabrieksmethoden zoals Collections.reverseOrder(Comparator<T>)
. Het reverseOrder()
van de methode mag geen implementatiedetails onthullen, bijv. reverseOrder()
wordt geïmplementeerd zoals
public static <T> Comparator<T> reverseOrder(Comparator<T> cmp) {
// (Irrelevant lines left out.)
return new ReverseComparator2<>(cmp);
}
Voorbeeld van strategiepatroon in Java met contextklasse
Strategie:
Strategy
is een gedragspatroon, waarmee het algoritme dynamisch kan worden gewijzigd uit een familie van gerelateerde algoritmen.
UML van strategiepatroon van 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);
}
}
output:
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
Probleemstelling: bied 25% korting op de prijs van het artikel voor de maanden juli-december. Geef geen korting voor de maanden januari-juni.
Bovenstaand voorbeeld toont het gebruik van Strategy
met Context
. Context
kan worden gebruikt als Single Point of Contact voor de Client
.
Twee strategieën - NoOfferStrategy
en QuarterDiscountStrategy
zijn per probleemverklaring verklaard.
Zoals weergegeven in de uitvoerkolom, krijgt u korting afhankelijk van de maand die u hebt ingevoerd
Gebruik case (s) voor strategiepatroon :
Gebruik dit patroon wanneer u een reeks uitwisselbare algoritmen hebt en u het algoritme tijdens runtime moet wijzigen.
Houd de code schoner door voorwaardelijke verklaringen te verwijderen
Strategiepatroon zonder contextklasse / Java
Het volgende is een eenvoudig voorbeeld van het gebruik van het strategiepatroon zonder contextklasse. Er zijn twee implementatiestrategieën die de interface implementeren en hetzelfde probleem op verschillende manieren oplossen. Gebruikers van de klasse EnglishTranslation kunnen de vertaalmethode oproepen en kiezen welke strategie ze voor de vertaling willen gebruiken, door de gewenste strategie op te geven.
// 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
}
}
Gebruik van functionele interfaces van Java 8 om het strategiepatroon te implementeren
Het doel van dit voorbeeld is om te laten zien hoe we het strategiepatroon kunnen realiseren met behulp van functionele interfaces van Java 8. We beginnen met een eenvoudige use case-code in het klassieke Java en hercoderen het vervolgens op de Java 8-manier.
Het voorbeeld probleem dat we gebruiken is een familie van algoritmen (strategieën), die verschillende manieren om te communiceren over een afstand te beschrijven.
De klassieke Java-versie
Het contract voor onze familie van algoritmen wordt bepaald door de volgende interface:
public interface CommunicateInterface {
public String communicate(String destination);
}
Dan kunnen we een aantal algoritmen als volgt implementeren:
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..";
}
}
Deze kunnen als volgt worden geïnstantieerd:
CommunicateViaPhone communicateViaPhone = new CommunicateViaPhone();
CommunicateViaEmail communicateViaEmail = new CommunicateViaEmail();
CommunicateViaVideo communicateViaVideo = new CommunicateViaVideo();
Vervolgens implementeren we een service die de strategie gebruikt:
public class CommunicationService {
private CommunicateInterface communcationMeans;
public void setCommuncationMeans(CommunicateInterface communcationMeans) {
this.communcationMeans = communcationMeans;
}
public void communicate(String destination) {
this.communcationMeans.communicate(destination);
}
}
Tot slot kunnen we de verschillende strategieën als volgt gebruiken:
CommunicationService communicationService = new CommunicationService();
// via phone
communicationService.setCommuncationMeans(communicateViaPhone);
communicationService.communicate("1234567");
// via email
communicationService.setCommuncationMeans(communicateViaEmail);
communicationService.communicate("[email protected]");
Gebruik van Java 8 functionele interfaces
Het contract van de verschillende algoritme-implementaties heeft geen speciale interface nodig. In plaats daarvan kunnen we het beschrijven met behulp van de bestaande java.util.function.Function<T, R>
interface.
De verschillende algoritmen die the family of algorithms
kunnen worden uitgedrukt als lambda-uitdrukkingen. Dit vervangt de strategieklassen en hun instantiaties.
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..";
Vervolgens kunnen we de "service" als volgt coderen:
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);
}
}
Ten slotte gebruiken we de strategieën als volgt
CommunicationService communicationService = new CommunicationService();
// via phone
communicationService.setCommuncationMeans(communicateViaPhone);
communicationService.communicate("1234567");
// via email
communicationService.setCommuncationMeans(communicateViaEmail);
communicationService.communicate("[email protected]");
Of zelfs:
communicationService.setCommuncationMeans(
destination -> "communicating " + destination + " via Smoke signals.." );
CommunicationService.communicate("anyone");
Strategie (PHP)
Voorbeeld van 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;
}
}