战略模式的真实世界范例

我一直在阅读关于 OCP 原理以及如何使用策略模式来实现这一点。

我打算试着向一些人解释这个问题,但是我能想到的唯一一个例子是根据“订单”的状态使用不同的验证类。

I've read a couple of articles online, but these don't usually describe a real alike reason to use the strategy, like generating reports/bills/validation, etc...

在现实世界中有没有你认为战略模式很常见的例子?

98467 次浏览

What about this:

You have to encrypt a file.

For small files, you can use "in memory" strategy, where the complete file is read and kept in memory ( let's say for files < 1 gb )

For large files, you can use another strategy, where parts of the file are read in memory and partial encrypted results are stored in tmp files.

These may be two different strategies for the same task.

The client code would look the same:

 File file = getFile();
Cipher c = CipherFactory.getCipher( file.size() );
c.performAction();






// implementations:
interface  Cipher  {
public void performAction();
}


class InMemoryCipherStrategy implements Cipher {
public void performAction() {
// load in byte[] ....
}
}


class SwaptToDiskCipher implements Cipher {
public void performAction() {
// swapt partial results to file.
}


}

The

     Cipher c = CipherFactory.getCipher( file.size() );

Would return the correct strategy instance for the cipher.

I hope this helps.

( I don't even know if Cipher is the right word :P )

I have an application that synchronizes it's user base each day against our enterprise directory. User's are eligible or not eligible based on their status in the University. Each day the provisioning program goes through and makes sure that those who are supposed to be eligible are provisioned in the application and those who are not are de-provisioned (actually according to a graceful degradation algorithm, but that's beside the point). On Saturday I do a more thorough update that synchronizes some properties of each user as well as making sure that they have the proper eligibility. At the end of the month I do some bill back processing based on usage for that month.

I use a composable strategy pattern to do this synchronization. The main program basically chooses a master strategy depending on the day of the week (sync changes only/sync all) and the time of semester relative to the academic calendar. If the billing cycle is ending, then it also composes it with a billing strategy. It then runs the chosen strategy via a standard interface.

I don't know how common this is, but I felt like it was a perfect fit for the strategy pattern.

I used the strategy approach in a fairly complex engine in an application that is a good example. Essentially the engine's role was to go and first find a list of people who had a widget, it's second role was to figure out which were the 10 best people with a widget based on an unknown number of parameters (things like price distance previous business together, ammount on stock, shipping options etc etc etc...)

Essentially what we did was we broke the problem into two strategies the first being data retrieval, as we knew that we had multiple sources of our widgets and we needed to be able to get the data and transform it to a common structure.

We then also realized that we had multiple algorithims some were based on weighting the parameters, others were very weird and propitery and I couldn't do them justice without pulling out visios and charts and well you get the picture, we had lots of algorithims for selecting the best people.

Our service itself was very thing it essentially defined the inputs, outputs and did some normalization of the data, it also used a provider pattern to plug-in the application specific data providers and algorithim providers which used the strategy. This was a fairly effective system.

We had some debates if we were using a strategy or a template pattern which we never resolved.

A few weeks ago, I added a common Java interface which was implemented by one of our domain objects. This domain object was loaded from the database, and the database representation was a star schema with about 10+ branches. One of the consequences of having such a heavy weight domain object is that we've had to make other domain objects that represented the same schema, albeit less heavyweight. So I made the other lightweight objects implement the same interface. Put otherwise we had:

public interface CollectibleElephant {
long getId();
String getName();
long getTagId();
}


public class Elephant implements CollectibleElephant { ... }
public class BabyElephant implements CollectibleElephant { ... }

Originally, I wanted to use CollectibleElephant to sort Elephants. Pretty quickly, my teammates glommed onto CollectibleElephant to run security checks, filter them as they get sent to the GUI, etc.

Are you sure that the status of an "order" is not a State pattern? I have a hunch that an order will not be handled differently depending on its status.

Take for example the method Ship on the Order:

order.Ship();
  • If the shipping method varies in function of its status, then you've got a strategy pattern.
  • If however the Ship() method succeeds only when the order has been paid, and the order has not been shipped yet, you've got a state pattern.

The best example of the state pattern (and other patterns) I found was in the book "Head First Design Patterns", which is amazing. A close second will be David Cumps' blogging series of patterns.

We had to create a third-party provisioning interface for an enterprise platform with a very complicated database. The submission of data to be provisioned was as a list of our data types which were put into a priority queue in our application so they could be written to the database in the correct order due to dependencies.

The process to write that data was then quite simple, keep popping off the top of the priority queue and then choose a strategy based on the type of the object that you extract.

I can think of several fairly simple examples:

  • Sorting a list. The strategy is the comparison used to decide which of two items in the list is "First"
  • You might have an application where the sorting algorithm itself (QuickSort, HeapSort, etc.) may be chosen at runtime
  • Appenders, Layouts, and Filters in Log4Net and Log4j
  • Layout Managers in UI toolkits
  • Data compression. You might have an ICompressor interface whose sole method looks something like this:

    byte[] compress(byte[] input);

    Your concrete compression classes might be things like RunLengthCompression, DeflateCompression, etc.

One common usage of the strategy pattern is to define custom sorting strategies (in languages without higher-order functions), e.g. to sort a list of strings by length in Java, passing an anonymous inner class (an implementation of the strategy interface):

List<String> names = Arrays.asList("Anne", "Joe", "Harry");
Collections.sort(names, new Comparator<String>() {
public int compare(String o1, String o2) {
return o1.length() - o2.length();
}
});
Assert.assertEquals(Arrays.asList("Joe", "Anne", "Harry"), names);

In a similar manner, strategies can be used for native queries with object databases, e.g. in db4o:

List<Document> set = db.query(new Predicate<Document>() {
public boolean match(Document candidate) {
return candidate.getSource().contains(source);
}
});

I know this is an old question, but I think I have another interesting example that I implemented recently.

This is a very practical example of the strategy pattern being used in a document delivery system.

I had a PDF delivery system which received an archive containing lots of documents and some metadata. Based on the metadata, it decided where to put the document in; say, depending on the data, I could store the document in A, B, or C storage systems, or a mix of the three.

Different customers used this system, and they had different rollback / error handling requirements in case of errors: one wanted the delivery system to stop on the first error, leave all documents already delivered in their storages, but stop the process and not deliver anything else; another one wanted it to rollback from B in case of errors when storing in C, but leave whatever was already delivered to A. It's easy to imagine that a third or fourth one will also have different needs.

To solve the problem, I have created a basic delivery class that contains the delivery logic, plus methods for rolling back stuff from all storages. Those methods are not actually called by the delivery system directly in case of errors. Instead, the class uses Dependency Injection to receive a "Rollback / Error Handling Strategy" class (based on the customer using the system), which is called in case of errors, which in turn calls the rollback methods if it's appropriate for that strategy.

The delivery class itself reports what's going on to the strategy class (what documents were delivered to what storages, and what failures happened), and whenever an error occurs, it asks the strategy whether to continue or not. If the strategy says "stop it", the class calls the strategy's "cleanUp" method, which uses the information previously reported to decide which rollback methods to call from the delivery class, or simply do nothing.

rollbackStrategy.reportSuccessA(...);
rollbackStrategy.reportFailureB(...);


if (rollbackStrategy.mustAbort()) {
rollbackStrategy.rollback(); // rollback whatever is needed based on reports
return false;
}

So I now have two different strategies: one is the QuitterStrategy (which quits on the first error and cleans up nothing) and the other one is the MaximizeDeliveryToAStrategy (which tries as much as possible not to abort the process and never rollback stuff delivered to storage A, but rollbacks stuff from B if delivery to C fails).

From my understanding, this is one example of the strategy pattern. If you (yes, you reading) think I'm wrong, please comment below and let me know. I'm curious as to what would constitute a "pure" use of the strategy pattern, and what aspects of my implementation violate the definition. I think it looks a bit funny because the strategy interface is a bit fat. All examples I've seen so far use only one method, but I still think this encapsulates an algorithm (if a piece of business logic can be considered an algorithm, which I think it does).

Since the strategy is also notified about events during the delivery execution, it can also be considered an Observer, but that's another story.

From doing a little research, it seems like this is a "composite pattern" (like MVC, a pattern that uses multiple design patterns underneath in a particular way) called the Advisor. It's an advisor on whether the delivery should continue or not, but it's also an active error handler since it can rollback stuff when asked to.

Anyways, this is a quite complex example that might make that feeling of yours that usages of the strategy pattern are all too simple / silly. It can be really complex and even more applicable when used together with other patterns.

Again, an old post but still turns up on searches so I'll add two more examples (Code is in C#). I absolutely love the Strategy pattern since it has saved my butt a lot of times when the project managers say: "We want the application to do 'X', but 'X' is not yet clear and it can change in the near future." This video explaining the strategy pattern, uses StarCraft as an example.

Stuff that falls in this category:

  • Sorting: We want to sort these numbers, but we don't know if we are gonna use BrickSort, BubbleSort or some other sorting

  • Validation: We need to check items according to "Some rule", but it's not yet clear what that rule will be, and we may think of new ones.

  • Games: We want player to either walk or run when he moves, but maybe in the future, he should also be able to swim, fly, teleport, burrow underground, etc.

  • Storing information: We want the application to store information to the Database, but later it may need to be able to save a file, or make a webcall

  • Outputting: We need to output X as a plain string, but later may be a CSV, XML, JSON, etc.


Examples

I have a project where the users can assign products to people in a database. This assignment of a product to a person has a status which is either "Approved" or "Declined", which is dependent on some business rules. For example: if a user assigns a product to a person with a certain age, it's status should be declined; If the difference between two fields in the item is larger than 50, it's status is declined, etc.

Now, at the moment of development these business rules are not yet all completely clear, and new rules could come up at any time. The power of the stragety-pattern is that I made a RuleAgent, which is given a list of IRules.

public interface IRule {
bool IsApproved(Assignment assignment);
}

At the moment of assigning a product to a person, I create a RuleAgent, give it a list of rules (which all implement IRule), and ask it to validate an assignment. It'll run through all it's rules. Which, because they all implement the same interface, all have the IsApproved method and return false if any of them returns false.

Now when for instance the manager suddenly comes up and says, we also need to decline all assignments to interns, or all assignments to people working overtime... You make new classes like this:

public OvertimeRule : IRule
{
public bool IsApproved(Assignment assignment) //Interface method
{
if (assignment.Person.Timesheet >= 40)
{
return false;
}
return true;
}
}


public InternRule : IRule
{
public bool IsApproved(Assignment assignment) //Interface method
{
if (assignment.Person.Title == "Intern")
{
return false;
}
return true;
}
}

You see that you don't have to keep adding or removing if-statements or code, just make a new rule-class that implements the IRUle interface and switch those out when needed.


Another great example: Scott Allen's video series at http://www.asp.net/mvc/pluralsight where he uses the strategy pattern in the Unit-test part of the application

He builds a website which has a page that displays items based on popularity. However "Popular" can be many things (most views, most subscribers, creation date, most activity, least amount of comments, etc), and in case management doesn't yet know exactly how to order, and may want to experiment with different orderings at a later date. You make an interface (IOrderAlgorithm or something) with an order method, and let an Orderer-object delegate the ordering to a concrete implementation of the IOrderAlgorithm interface. You can make a "CommentOrderer", "ActivityOrderer", etc... And just switch these out when new requirements come up.

Let's say you want to write an algorithm to calculate the nth Xday of a given month and year, e.g., the second Monday of October 2014. You want to use Android's Time class android.text.format.Time to represent the date, but you also want to write a generic algorithm that can also apply to java.util.Calendar.

This is what I did.

In DatetimeMath.java:

public interface DatetimeMath {
public Object createDatetime(int year, int month, int day);


public int getDayOfWeek(Object datetime);


public void increment(Object datetime);
}

In TimeMath.java:

public class TimeMath implements DatetimeMath {
@Override
public Object createDatetime(int year, int month, int day) {
Time t = new Time();
t.set(day, month, year);
t.normalize(false);
return t;
}


@Override
public int getDayOfWeek(Object o) {
Time t = (Time)o;
return t.weekDay;
}


@Override
public void increment(Object o) {
Time t = (Time)o;
t.set(t.monthDay + 1, t.month, t.year);
t.normalize(false);
}
}

In OrdinalDayOfWeekCalculator.java, the class with the generic algorithm:

public class OrdinalDayOfWeekCalculator {
private DatetimeMath datetimeMath;


public OrdinalDayOfWeekCalculator(DatetimeMath m) {
datetimeMath = m;
}


public Object getDate(int year, int month, int dayOfWeek, int ordinal) {
Object datetime = datetimeMath.createDatetime(year, month, 1);
if (datetimeMath.getDayOfWeek(datetime) == dayOfWeek) {
return datetime;
}
int xDayCount = 0;
while (xDayCount != ordinal) {
datetimeMath.increment(datetime);
if (datetimeMath.getDayOfWeek(datetime) == dayOfWeek) {
xDayCount++;
}
}
return datetime;
}
}

In my Android app, I would call something like

OrdinalDayOfWeekCalculator odowc =
new OrdinalDayOfWeekCalculator(new TimeMath());
Time canadianThanksgiving = (Time)odowc.getDate(
year, Calendar.OCTOBER, Time.MONDAY, 2);

If I want to reuse the same algorithm for java.util.Calendar, I would just write a class CalendarMath that implements the three methods in DatetimeMath and then use

OrdinalDayOfWeekCalculator odowc2 =
new OrdinalDayOfWeekCalculator(new CalendarMath());
Calendar canadianThanksgivingCal = (Calendar)odowc2.getDate(
year, Calendar.OCTOBER, Calendar.MONDAY, 2);

A good example of strategy pattern would be in a game where we can have different characters and each character can have multiple weapons to attack but at a time can use only one weapon. So we have the character as the context, for example King, Commander, Knight ,Soldier and weapon as a strategy where attack() could be the method/algorithm which depends on the weapons being used. So if the concrete weapon classes were Sword, Axe, Crossbow, BowAndArrow etc .. they would all implement the attack() method. I am sure further explanation is not needed.

Strategy pattern is most commonly used pattern specially for validations and sorting algorithms.

Let me explain with a simple practical example

enum Speed {
SLOW, MEDIUM, FAST;
}


class Sorter {
public void sort(int[] input, Speed speed) {
SortStrategy strategy = null;
switch (speed) {
case SLOW:
strategy = new SlowBubbleSortStrategy();
break;
case MEDIUM:
strategy = new MediumInsertationSortStrategy();
break;


case FAST:
strategy = new FastQuickSortStrategy();
break;
default:
strategy = new MediumInsertationSortStrategy();
}
strategy.sort(input);
}


}


interface SortStrategy {


public void sort(int[] input);
}


class SlowBubbleSortStrategy implements SortStrategy {


public void sort(int[] input) {
for (int i = 0; i < input.length; i++) {
for (int j = i + 1; j < input.length; j++) {
if (input[i] > input[j]) {
int tmp = input[i];
input[i] = input[j];
input[j] = tmp;
}
}
}
System.out.println("Slow sorting is done and the result is :");
for (int i : input) {
System.out.print(i + ",");
}
}


}


class MediumInsertationSortStrategy implements SortStrategy {


public void sort(int[] input) {
for (int i = 0; i < input.length - 1; i++) {
int k = i + 1;
int nxtVal = input[k];
while (input[k - 1] > nxtVal) {
input[k] = input[k - 1];
k--;
if (k == 0)
break;
}
input[k] = nxtVal;
}
System.out.println("Medium sorting is done and the result is :");
for (int i : input) {
System.out.print(i + ",");
}


}


}


class FastQuickSortStrategy implements SortStrategy {


public void sort(int[] input) {
sort(input, 0, input.length-1);
System.out.println("Fast sorting is done and the result is :");
for (int i : input) {
System.out.print(i + ",");
}
}


private void sort(int[] input, int startIndx, int endIndx) {
int endIndexOrig = endIndx;
int startIndexOrig = startIndx;
if( startIndx >= endIndx)
return;
int pavitVal = input[endIndx];
while (startIndx <= endIndx) {
while (input[startIndx] < pavitVal)
startIndx++;
while (input[endIndx] > pavitVal)
endIndx--;
if( startIndx <= endIndx){
int tmp = input[startIndx];
input[startIndx] = input[endIndx];
input[endIndx] = tmp;
startIndx++;
endIndx--;
}
}
sort(input, startIndexOrig, endIndx);
sort(input, startIndx, endIndexOrig);
}


}

The test code for this is

public class StrategyPattern {
public static void main(String[] args) {
Sorter sorter = new Sorter();
int[] input = new int[] {7,1,23,22,22,11,0,21,1,2,334,45,6,11,2};
System.out.print("Input is : ");
for (int i : input) {
System.out.print(i + ",");
}
System.out.println();
sorter.sort(input, Speed.SLOW);
}


}

Same example is taken from http://coder2design.com/strategy-pattern/

Key notes:

  1. Strategy is behavioral design pattern. It is used to switch between family of algorithms.

  2. This pattern contains one abstract strategy interface and many concrete strategy implementations (algorithms) of that interface.

  3. The application uses strategy interface only. Depending on some configuration parameter, the concrete strategy will be tagged to interface.

UML Diagram from wikipedia

enter image description here

One real word example : Airlines offering discounts during some months (July-December). You can have one Fare module, which decides pricing options depending on month number.

Have a look at a simple example. This example can be extended to on-line retailing applications, which provides discount to shopping cart items on special days/happy hours easily.

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 use 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

Useful articles:

strategy pattern by dzone

strategy pattern by sourcemaking

public class StrategyDemo {
public static void main(String[] args) {
ShoppingCart cart = new ShoppingCart();


Item item1 = new Item("1234", 10);
Item item2 = new Item("5678", 40);


cart.addItem(item1);
cart.addItem(item2);


// pay by paypal
cart.pay(new PaypalStrategy("myemail@example.com", "mypwd"));


// pay by credit card
cart.pay(new CreditCardStrategy("Pankaj Kumar", "1234567890123456", "786", "12/15"));
}
}


interface PaymentStrategy {
public void pay(int amount);
}


class CreditCardStrategy implements PaymentStrategy {


private String name;
private String cardNumber;
private String cvv;
private String dateOfExpiry;


public CreditCardStrategy(String nm, String ccNum, String cvv, String expiryDate) {
this.name = nm;
this.cardNumber = ccNum;
this.cvv = cvv;
this.dateOfExpiry = expiryDate;
}


@Override
public void pay(int amount) {
System.out.println(amount + " paid with credit/debit card");
}


}


class PaypalStrategy implements PaymentStrategy {


private String emailId;
private String password;


public PaypalStrategy(String email, String pwd) {
this.emailId = email;
this.password = pwd;
}


@Override
public void pay(int amount) {
System.out.println(amount + " paid using Paypal.");
}


}


class Item {


private String upcCode;
private int price;


public Item(String upc, int cost) {
this.upcCode = upc;
this.price = cost;
}


public String getUpcCode() {
return upcCode;
}


public int getPrice() {
return price;
}


}


class ShoppingCart {


// List of items
List<Item> items;


public ShoppingCart() {
this.items = new ArrayList<Item>();
}


public void addItem(Item item) {
this.items.add(item);
}


public void removeItem(Item item) {
this.items.remove(item);
}


public int calculateTotal() {
int sum = 0;
for (Item item : items) {
sum += item.getPrice();
}
return sum;
}


public void pay(PaymentStrategy paymentMethod) {
int amount = calculateTotal();
paymentMethod.pay(amount);
}
}

From wikipedia

In computer programming, the strategy pattern (also known as the policy pattern) is a behavioral software design pattern that enables selecting an algorithm at runtime. Instead of implementing a single algorithm directly, code receives run-time instructions as to which in a family of algorithms to use

In Windows Paint application you can see a strategy pattern where you can choose the shape and color independently in a different sections. Here the shape and color are the algorithms which can changed at runtime.

If you want to draw a circle with red color, rather than providing an option of 'RedCircle' they let you choose the circle and a color of your choice.

Shape redCircle = new RedCircle(); // Without stretegy Pattern
Shaped redCircle = new Shape("red","circle"); // With Strategy pattern

Without strategy pattern will increase the number of classes with the Cartesian product of shape and color. Also the interface changes for each implementation.

Imagine a shooter game with AI enemies for example. You want them to continuously fight in different ways based on what happens .. With strategy pattern you can continuously loop and dynamically change how a specific or action will be performed.

interface FightingStategy{
public void fight();
}
public Defense implements FightingStrategy{
public void figth(){
... hide behind wall to shoot
}
}
public Berserker implements FightingStrategy{
public void fight(){
... run towards you, headrolls and shoots
}
}
public Dead implements FightingStrategy{
public void fight(){
... is dead, doesn't move
}
}


public AiShooter{


FightingStrategy fightingStrategy;


public AiShooter(){
fightStrategy = new Berserker();
}


public void fight(){
this.fightingStrategy.fight();
}


public void changeStrategy(FightingStrategy f){
this.fightingStrategy = f;
}
}


public static void main(){


... create list of AiShooters...
while (condition){
list.forEach(shooter -> shooter.fight());
}
... you shoot back
list.ForEach(shooter -> shooter.changeStrategy(new
Defense()));


... you kill one
list.get(n).changeStrategy(new Dead());
}

Example to calculate Item GST Tax Total

public interface TaxCalculation {


public Double calculateTax(Double price);
}
public class FivePercentage implements TaxCalculation {


@Override
public Double calculateTax(Double price) {
        

Double dbl = (price*5)/100;
return dbl;
}


}
public class EighteenPercentage implements TaxCalculation {


@Override
public Double calculateTax(Double price) {
Double dbl = (price*18)/100;
return dbl;
}


}
public class Item {


public String name;
public Double price;
public int taxRate;
public Double totalTax;
    

public Item(String name, Double price, int taxRate) {
super();
this.name = name;
this.price = price;
this.taxRate = taxRate;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Double getPrice() {
return price;
}
public void setPrice(Double price) {
this.price = price;
}
public int getTaxRate() {
return taxRate;
}


public void setTaxRate(int taxRate) {
this.taxRate = taxRate;
}


public Double getTotalTax() {
return totalTax;
}


public void setTotalTax(Double totalTax) {
this.totalTax = totalTax;
}


public void calculateTax(TaxCalculation taxcalulation, Double price) {
this.totalTax = taxcalulation.calculateTax(price);
}
    

@Override
public String toString() {
return "Items [name=" + name + ", price=" + price + ", taxRate=" + taxRate + ", totalTax=" + totalTax + "]";
}
    

}
public class CalculateTax {


public static void main(String[] args) {
        

List<Item> itemList = new ArrayList<>();
        

Item item1 = new Item("Engine Oil", 320.0, 5);
Item item2 = new Item("Painting", 3500.00, 18);
        

itemList.add(item1);
itemList.add(item2);
        

itemList.stream().forEach(x-> {
if(x.getTaxRate() == 5) {
x.calculateTax(new FivePercentage(), x.getPrice());
} else if(x.getTaxRate() == 18) {
x.calculateTax(new EighteenPercentage(), x.getPrice());
}
});
        

itemList.stream().forEach(x-> {
System.out.println(x.toString());
});
}
}

This answer is for someone who is a beginner and want to understand this with the simplest example possible - (Reference : Headfirst Design Patterns)

#include<iostream>
using namespace std;


/*
Where it is applicable?
The selection of an algorithm is required from a family of algorithms.
To avoid multiple conditional statements for selection of algorithms
and to hide its algorithm data structures and complexity from client.
*/


class Fly {
public:
virtual void fly() = 0;
};


//concrete Fly : rocketFly
class rocketFly : public Fly {
public:
void fly() {
cout <<"rocketFly::fly()" << endl;
}
};


//concrete Fly : normalFly
class normalFly : public Fly {
public:
void fly() {
cout <<"normalFly::fly()" << endl;
}
};


//Duck "HAS A" relationship with Fly
class Duck {
private:
//Duck has a Fly behavour
Fly* flyObj;
public:
Duck(Fly* obj) : flyObj(obj) {
        

}


void DuckFly() {
flyObj->fly();
}
};


int main() {
rocketFly* rObj = new rocketFly;
Duck wildDuck(rObj);
wildDuck.DuckFly();


normalFly* nObj = new normalFly;
Duck cityDuck(nObj);
cityDuck.DuckFly();


/*
I didn't have to create classes like wildDuck which inherits from Duck and they implement their own
fly, quack etc behaviour. There will be code duplication.
So, instead of that, create an interface of fly, make concrete fly classes with different
fly behaviour. Use objects to any of these concrete classes to inject to one generic duck
class which will automatically call correct fly behaviour.
*/


return 0;
}