The selection of which algorithm, policy, or business rule a method should be used might depend on information that is only available at runtime.
For example, an invoice object for an e-commerce application might provide a method for computing sales tax. However, the algorithm used by this will depend on the state that the purchase order comes from. One solution is to introduce an invoice subclass for each state and override the computeTax method in each of these classes:
abstract class Invoice {
protected List<LineItem>
itemsPurchased;
public Money getTotal() {
Money total = new Money(0);
for(LineItem item: itemsPurchased) {
total = total.add(item.getCount()
* item.getCost());
}
total = total.add(getSalesTax());
return total;
}
abstract protected Money getSalesTax();
// etc.
}
class OregonInvoice extends Invoice {
// no sales tax in Oregon!
public Money getTax() { return new
Money(0); }
}
// etc.
This leads to a large hierarchy that will need even more subclasses when orders from abroad are accepted.
Instead of subclassing an abstract Invoice class, we should introduce an abstract GetSalesTax class.
abstract class GetSalesTax {
public abstract Money
getSalesTax(Invoice inv);
}
class Invoice {
private List<LineItem>
itemsPurchased;
private GetSalesTax strategy;
public void setStrategy(GetSalesTax
gst) {
strategy = gst;
}
public GetSalesTax getStrategy() {
return strategy;
}
public Money getTotal() {
Money total = new Money(0);
for(LineItem item: itemsPurchased) {
total = total.add(item.getCount()
* item.getCost());
}
total =
total.add(strategy.getSalesTax(this));
return total;
}
// etc.
}
Note that this also leads to a large hierarchy. But there are several advantages:
1. We can dynamically change the sales tax calculation strategy used by a single invoice. This isn't so important in this example, but, for example, it would allow us to compare the cost of the same invoice in several states. It would also make it easier to experiment with new sales tax strategies.
2. The sales tax hierarchy is potentially reusable by other objects that might need to calculate sales taxes. In fact, the GetSalesTax objects for each state can be reused. It might also be easier to test.
3. The strategy pattern encapsulates the aspect of invoices that changes. Thus, invoices are open for extension, but closed for modification.
4. Suppose Invoice must vary in another way. For example, suppose we need to add shipping cost to the total:
public Money getTotal() {
Money total = new Money(0);
for(LineItem item: itemsPurchased) {
total = total.add(item.getCount()
* item.getCost());
}
total = total.add(getSalesTax());
total =
total.add(getShippingCost());
return total;
}
But shipping cost will vary according to the shipper used: DHL, UPS, FedEx, etc. If we had introduced 50 subclasses, one for each state that overrides getSalesTax, then we must now add three subclasses to each of these 50, one for each shipper used. That not only leads to a total of 140 subclasses, but it also leads to a lot of duplicated code since for many shippers it costs the same to ship to California as it does to Oregon.
Instead, we can simply introduce a hierarchy of Shipper classes that implement a Shipper interface:
interface Shipper {
Money getShippingCost();
}
In addition to a sales tax strategy, we can equip our Invoice class with a shipping strategy:
class Invoice {
private List<LineItem>
itemsPurchased;
private GetSalesTax taxStrategy;
private Shipper shippingStrategy;
public Money getTotal() {
Money total = new Money(0);
for(LineItem item: itemsPurchased) {
total = total.add(item.getCount()
* item.getCost());
}
total =
total.add(TaxStrategy.getSalesTax(this));
total = total.add(shipper.getShippingCost(this));
return total;
}
// etc.
}
It's interesting to contrast the strategy pattern with the decorator pattern. In the decorator pattern the base-level object is a body, while decorators are handles that add some behavior, then delegate to their bodies, which may be the base-level object or still other decorators:
In the strategy pattern the base-level object is a handle that delegates to its body, a strategy that determines the base-level object's behavior:
Gamma, et. al. [Go4] express the comparison more colorfully:
"A decorator lets you change the skin of an object, a strategy lets you change the guts."
It's also interesting to compare the strategy pattern with the generic algorithm pattern. Recall that in the generic algorithm pattern the details of a class method were implemented in an extension class, while in the strategy pattern the details are determined by an associated strategy class.
The strategy pattern is structurally similar to the state pattern. A base-level object plays the role of the context, which delegates to a strategy object that plays the role of the state. All strategies are instances of classes derived from an abstract strategy base class, which specifies the meta interface. However, the strategy pattern is different from the state pattern, because many objects, possibly instantiating different classes, may share the same strategy object.
The strategy pattern is used in Java to layout containers. Recall that every container (in awt or in Swing) has an add method that allows programmers to add panels and controls to the container's content pane:
class GUI extends JFrame {
public GUI() {
Container contentPane = getContentPane();
contentPane.add(new
Button("STOP")):
contentPane.add(new
Button("GO"));
// etc.
}
}
How does the add() method know where to add the controls? Recall that this is determined by a layout manager associated with awt's Container class:
Programmers can set the layout manager using the setLayout method:
setLayout(new FlowLayout());
As another example, a financial application might represent investments as objects encapsulating the initial amount of the investment (i.e., the principle). A value() member function computes the future value of the investment after n months, but of course this function depends on the risk and yield of the selected investment strategy: