Lives in the UK. Likes blogging, cycling and eating lemon drizzle cake. Roger is a DZone MVB and is not an employee of DZone and has posted 143 posts at DZone. You can read more from them at their website. View Full User Profile

The Strategy Pattern

03.31.2012
| 7875 views |
  • submit to reddit
In a recent blog on I received a comment from Wojciech Soczyński about how the “strategy” pattern can be used to enforce the Single Responsibility Principle (SRP) when using Tell Don't Ask (TDA). At some point I plan to discuss this further, but first thought that it would be a good idea to define the Strategy Pattern using the ShoppingCart example that I used a couple of weeks ago in my Tell Don’t Ask and its follow up Disassembling Tell Don’t Ask blogs:



First a definition: in the simplest terms, you can define the Strategy Pattern as telling an object to do a job and to do it using ANOTHER object.

To clarify this further I’m going to redesign the ShoppingCart slightly, by giving it a pay()1 method:

public class ShoppingCart {

  private final List<Item> items;

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

  public void addItem(Item item) {

    items.add(item);
  }

  public double calcTotalCost() {

    double total = 0.0;
    for (Item item : items) {
      total += item.getPrice();
    }

    return total;
  }

  public boolean pay(PaymentMethod method) {

    double totalCost = calcTotalCost();
    return method.pay(totalCost);
  }
}

The thing to notice about the pay() method is that it takes one parameter of type PaymentMethod - it’s the PaymentMethod that’s the “ANOTHER” object in my definition above.

The next thing to do is define the PaymentMethod as an interface. Why an interface? It’s because the power of this technique is that you can decide at run-time which concrete type you’ll pass into the ShoppingCart to make the payment. For example, given the Payment interface:

public interface PaymentMethod {

  public boolean pay(double amount);

}

you can then define any concrete payment object such as a Visa or a MasterCard for example:
public class Visa implements PaymentMethod {

  private final String name;
  private final String cardNumber;
  private final Date expires;

  public Visa(String name, String cardNumber, Date expires) {
    super();
    this.name = name;
    this.cardNumber = cardNumber;
    this.expires = expires;
  }

  @Override
  public boolean pay(double amount) {

    // Open Comms to Visa
    // Verify connection
    // Paybill using these details
    return true; // if payment goes through
  }

}

...and
public class MasterCard implements PaymentMethod {

  private final String name;
  private final String cardNumber;
  private final Date expires;

  public MasterCard(String name, String cardNumber, Date expires) {
    super();
    this.name = name;
    this.cardNumber = cardNumber;
    this.expires = expires;
  }

  @Override
  public boolean pay(double amount) {

    // Open Comms to Mastercard
    // Verify connection
    // Paybill using these details
    return true; // if payment goes through
  }

}

The final thing to do is to demonstrate this with the unit test: payBillUsingVisa
  @Test
  public void payBillUsingVisa() {

    ShoppingCart instance = new ShoppingCart();

    Item a = new Item("gloves", 23.43);
    instance.addItem(a);

    Item b = new Item("hat", 10.99);
    instance.addItem(b);

    Date expiryDate = getCardExpireyDate();
    PaymentMethod visa = new Visa("CaptainDebug", "1234234534564567", expiryDate);

    boolean result = instance.pay(visa);
    assertTrue(result);

  }

  private Date getCardExpireyDate() {
    Calendar cal = Calendar.getInstance();
    cal.clear();
    cal.set(2015, Calendar.JANUARY, 21);
    return cal.getTime();
  }

In the code above, you can see that I’m creating a ShoppingCart and then I add a few items. Finally, I create a new PaymentMethod in the form of a Visa object and inject it into the pay(PaymentMethod method) function, which is the crux of the matter. In a different situation I could have easily created a MasterCard object and used that as a direct replacement for Visa - i.e. the object that which is passed in as an argument is determined at runtime.

And that defines the Strategy pattern, but that's not the end of the blog. If you've ever used Spring, but never heard of the Strategy pattern, all this should feel a little familiar. This is because it turns out that the Guys at Spring use the Strategy Pattern to underpin their whole technology. If I take my example above and make a few slight changes I can come up with:

@Component
public class SpringShoppingCart {

  private final List<Item> items;

  @Autowired
  @Qualifier("Visa")
  private PaymentMethod method;

  public SpringShoppingCart() {
    items = new ArrayList<Item>();
  }

  public void addItem(Item item) {

    items.add(item);
  }

  public double calcTotalCost() {

    double total = 0.0;
    for (Item item : items) {
      total += item.getPrice();
    }

    return total;
  }

  public boolean pay() {

    double totalCost = calcTotalCost();
    return method.pay(totalCost);
  }
}

The only difference between this incarnation and the first one is that the strategy class Visa is injected by Spring when the class is loaded using the @Autowired annotation. To sum this up, I guess guess that this means that the Strategy Pattern is the most popular pattern in the world.

1For the purposes of this discussion I’m assuming that it’s okay for a ShoppingCart to pay for itself, but whether this is correct or not is a whole new blog...
Published at DZone with permission of Roger Hughes, author and DZone MVB. (source)

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Comments

Witold Szczerba replied on Wed, 2012/04/04 - 5:03pm

Hi,

I really really do not get the examples you have provided. You have a class ShippingCart which is completely decoupled from a Visa and MasterCard classes. That is great, it lets you test ShippingCart with an abstract, mocked PaymentMethod object like this:

  • expectations: a paymentMethod mock should expect a pay($34.42) call,
  • preparations: create a shippingCart with 3 products: x($23.43), y($10.99),
  • execute: call shippingCart.pay(paymentMethod);

You are doing something completely different though. You are instantiating a real Visa object in order to see how does ShippingCart works. What is the reason for that? What does that test actually test? A Visa or ShippingCart? How is your test supposed to verify if the code it executes actually worked? Are you supposed to call a Visa operator and ask for recent operations each time you run your tests?

The last question opens yet another question. Your Visa and MasterCard implementations (both) state: all what we need in order to be able to operate is this:

  1. String name,
  2. String cardNumber,
  3. Date expires.

Is this really really true? How are the Visa and MasterCard objects supposed to actually make the money tranfer if all they ask for are name, number and date? Shouldn't they actually call some remote service as well? Hmm, looking at the method signatures proves otherwise. Magic, or did you hide some Singleton magic inside their bodies? If that is the case, how is your example test supposed to work?

Regards,
Witold Szczerba

P.S.
Using double for financial operations is not a good idea. The float and double types are approximate-number data types for use with floating point numeric data. Floating point data is approximate, therefore, not all values in the data type range can be represented exactly. That makes them very bad candidates for precise numeric values used in financial calculations.

Roger Hughes replied on Fri, 2012/04/06 - 3:44pm

Witold,

Thanks for the comments.  The important point to remember is that is a demonstration of the Strategy pattern; hence, the thing to remember is that this is only example code not a real life project. The tests just should how to use the Strategy Pattern rather than testing calcutions or payments. You're right about testing with real Visa and Mastercard objects - you just won't do it you'd use some kind of mock.

 Finally, yes you're right you wouldn't want to use floating point data types in a real life project.

 

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.