views:

282

answers:

3

Just a very general question, that not only applies to this example.

Let's say you have an Online Shop and you want to implement Vouchers/Gift Certificates, but with Constraints. Let's say you have a voucher for 20% off, but that applies only to products added within the last 3 weeks, but not to ones in a special promotion.

I see two ways to solve it: The first way is to code your shop to "natively" support all crazy types of vouchers. This seems to be the classic way, but it means a lot of work beforehand and very little flexibility (After all, you can't know beforehand what you need, and maybe Sales may come up with some really great new promotion which requires new vouchers - by next Monday).

The second way is a Plug-In way: Vouchers are like Plugins and each Voucher has it's own Code. You pass in the Shopping Basket into the Voucher and then the Voucher itself checks each item if it applies, makes the neccessary changes and returns the changed shopping cart.

I just wonder, what is the Design Pattern for Case 2? It looks a bit like IoC/DI, but then again not really because Vouchers are not replacing any existing functionality. It's more like a set of Object with a Special Interface (i.e. IVoucher), and then a Queue of IVoucher Object that gets iterated over. Is there a standard pattern (and best practice) for these types of "Manipulators"?

Edit: Thanks for the Answers. To clarify that just a bit, the Vouchers (or Manipulators - as said, this is not only a question about online shops but about a similar situations) are "heavy" objects, that is they have Business Logic in them. So I can say that a Voucher only applies if the Customer signed up before January 1 2008, only if the customer ordered at least 100$ in the past 6 months, only applies to articles in the Category X, "stacks" with other Vouchers except for Items marked as Reduced etc. etc. etc. So my concern was more about how to keep a clean structure to make sure the Vouchers get all that they need to check whether they apply and to be able to manipulate the Cart, so I wondered about what the standard for such situations are, which is exactly what the Visitor Pattern seems to do.

+1  A: 

Maybe the Visitor pattern? The different types of vouchers are the visitors, which visit the shopping basket and manipulate it.

I don't think IOC is the solution here.

Gerrie Schenck
+6  A: 

It's a case where you could use the strategy pattern along with the vistor pattern to calculate the value of the basket.

A vistor could visit each item in the basket utilising different strategies (in this case discount vouchers) and using those to calculate the full cost of the basket.

The vouchers used could be retrieved from a database in some way and injected into the visitor quite easily.

The voucher strategy could look something like this:

public interface IVoucher
{
    decimal CostOf(CartItem cartItem);
}

The default would be something like this:

public class FullPriceVoucher : IVoucher
{
    public decimal CostOf(CartItem cartItem)
    {
        return cartItem.Cost;   
    }
}

A 10% discount would be something like:

public class TenPercentOffVoucher : IVoucher
{
    public decimal CostOf(CartItem cartItem)
    {
        return cartItem.Cost * 0.9m;   
    }
}

Then you could have a visitor for calculating cart value like this:

public class CartValueVisitor
{
    private IVoucher voucher;

    public CartValueVisitor(IVoucher voucher)
    {
        this.voucher = voucher;
    }

    public decimal CostOf(Cart cart)
    {
        return cart.Items.Sum(item => voucher.CostOf(item));
    }
}

Which you would use like:

var cart = GetACart();

var fullPriceCartValueVisitor = 
        new CartValueVisitor(new FullPriceVoucher());
var tenPercentOffCartValueVisitor = 
        new CartValueVisitor(new TenPercentOffVoucher());

var fullPrice = fullPriceCartValueVisitor.CostOf(cart);
var tenPercentOffPrice = tenPercentOffCartValueVisitor.CostOf(cart);

This obviously only works with a single voucher at a time but should give you an idea of the general structure.

Garry Shutler
Can you just clarify if I an approach using a Queue<IVoucher> combined with a foreach-loop on that Queue would be the correct approach for multiple vouchers?
Michael Stum
Actually, the Visitor could hold a Collection<IVoucher>, which would mean: 1 Visitor, then a Queue of n Vouchers, and then the Visitor.CostOf() Function would just iterate over it. Thanks, I finally understood the Visitor Pattern and when it should be used :)
Michael Stum
Yes, but you have to be careful how you apply the vouchers obviously. You wouldn't necessarily want to apply every voucher to every item. For example if someone had a 10% off and a 50% off voucher you wouldn't want to apply both. That's why this example is a simple one :)
Garry Shutler
Well, possibly yes, but that's why I wanted "heavy" vouchers, because some Vouchers may "stack" while others won'tm some may only apply if you are ordering at least 7 items of a category or if you ordered for more than 80$ in the past etc. - essentially, the vouchers can have a Lot of Logic inside
Michael Stum
But that's essentially still the same case, I just refine my Interface to what I need and make sure that IVoucher has all the access and data it needs, and the Visitor connects everything together.
Michael Stum
Yeah, exactly. You'd probably have methods on the voucher for discounting the total value, shipping cost and so on. However, as you said the basic structure would be the same.
Garry Shutler
@Garry: Your code looks fine to me, but for the record I don't think it is using the Visitor pattern. What corresponds to accept()? What corresponds to visit()?
j_random_hacker
From what I understand, visit() is the CostOf() within the IVoucher, only the accept() is a bit shaky because it's mixed between the constructor and the CostOf Method. The Java Example in the Wikipedia article is interesting
Michael Stum
There's no visitor involved here. Visitors handle double dispatch by transforming dynamic dispatching into static overloading resolution; this isn't done here. Compare http://stackoverflow.com/questions/255214/when-should-i-use-the-visitor-design-pattern/255224
Konrad Rudolph
I'll have a look at that as well, thanks!
Michael Stum
+3  A: 

The previous answers suggesting Visitor and Strategy patterns sound fine to me, although Visitor is overkill in the typical case where each purchase item is an object of the same concrete class. The purpose of Visitor is to allow dynamic dispatch on two (or more) object types -- the visited objects are part of one hierarchy, and the visitors are part of another. But if only one object type (the concrete type of the class implementing IVoucher) varies, then regular old single-type virtual dispatch is all you need.

In fact I personally wouldn't bother with any "pattern" at all -- your own description is exactly what's needed: create an interface, IVoucher, and a bunch of classes that implement that interface. You'll also need a factory method that takes a voucher code and returns an IVoucher object having the appropriate concrete type.

Beware Non-Commutative Vouchers!

The fact that you mention a queue of IVoucher-implementing objects will be run against the purchase items implies that more than one voucher may be used. In this case you need to be careful -- does applying voucher A, then voucher B always have the same effect as applying B then A? Unfortunately many typical "special offers" would seem not to have this property (e.g. if voucher A gives you $10 off and voucher B gives you 5% off, the order definitely matters).

A quick and dirty way out of this is to assign each voucher a distinct numeric "priority" value, and always apply vouchers in priority value order. To reduce the probability of "weird" combinations of vouchers driving you bankrupt, it's probably also a good idea to limit voucher combinations to some set of allowed combinations specified in your code somewhere. (This could be as simple as a list of lists of voucher codes.)

j_random_hacker
Thanks. That's why I wanted "heavy" vouchers. Essentially, the voucher can check the whole cart (including all other vouchers and customer data) and access everything needed to validate itself, hence I wanted to know what the "proper" way to do it is, to avoid running against a wall later.
Michael Stum
Be wary of including logic that tests for other vouchers in a voucher's class. This is likely to mean continual changes to that voucher's code as new compatible/incompatible vouchers are born. If you maintain a separate list of allowed voucher combos instead, this won't happen.
j_random_hacker
There's still a maintenance burden, but the changes are localised rather than spread throughout different classes. Also, it occurs to me that by storing each allowed combo in a list, you can use the order that they are listed to specify the application order! Magic!
j_random_hacker
You would use a list of lists. Each sublist says: "any subset of these vouchers is allowed, and they will be applied in this order." E.g. if the list of lists contained ((A, B, C), (B, D)) then the following combos would be allowed: A, B, C, A+B, A+C, B+C, A+B+C, D, B+D.
j_random_hacker
Yes, I was thinking about a mechanism like that. There will always be a maintenance burden, but maintenance should be independent from the core system to make deployment as easy as possible by having a very loose coupling between the core and the vouchers.
Michael Stum