Design problem #2

This is a subset of the Back to the Checkout kata by Dave Thomas, which I used many times as a TDD training exercise.

Suppose you have a PriceRules object that knows that the prices of items. Its responsibility is to know the following table:

Item Unit Price Special Price
A 50 3 for 130
B 30 2 for 45
C 20
D 15

Then you have a Cart object that knows which items a customer is trying to buy. For instance, a given cart could contain the list [A, A, C, A, D].

The problem is to compute the total that the customer has to pay, out of a collaboration between (at least) the cart and the pricerRules objects. It seems easy, but there is a catch: you are forbidden to use getters. All methods must return “void”, in Java terms. Design the messages that are exchanged between the objects and produce the desired result. (In the example, it would be 165). Have fun!

17 Responses to “Design problem #2”

  1. fabio boldrini Says:

    Too easy to pass a Collector parameter or bypass getter with setter?

  2. matteo Says:

    Collector parameter is how I would solve it. I don’t know what “bypass getter with setter” is.

  3. fabio boldrini Says:

    a setFullPrice on the Cart, and a fillPrice(Cart) on the PriceRules that call the setFullPrice. Another way to see the collector parameter. In this way the two class are more coupled, I dont know it is better or worse (can dipende)

  4. Luca Minudel Says:

    thank you for publishing this exercise, I will get back with some source code soon (the tyme to copy it in pastie.org :)

  5. matteo Says:

    I see… you are implementing the responsibilities of the collecting parameter in the cart. I don’t mean it’s bad; the point of the exercise is to discuss alternatives! And the real test of the design comes when you see how well it handles the next changes :-)

  6. Luca Minudel Says:

    l’avevo sotttovalutato.

    questo il risultato: http://www.pastie.org/1005668

    ora voglio rivedere la relazione tra le tre classi Cashier, Bill e PriceRules.

    ottimo codice per un coding dojo sul TDD, grazie ancora!
    commenti e suggerimenti per migliorare il codice sono ben accetti.

  7. matteo Says:

    Luca: riesci a riformulare i metodi che si chiamano HandleXYZ e le classi che si chiamano XYZHandler senza usare la parola “Handle(r)” e in modo che il nome spieghi che cosa fanno?

  8. Luca Minudel Says:

    something like:

    public class Bill: IBill

    public void VisitBillGrandTotal(IBillGrandTotalVisitor visitor)
    {
    visitor.VisitGrandTotal(_grandTotal);
    }

    ?

  9. matteo Says:

    Luca,

    I still don’t find these names communicate your intent. What I mean is that a name like handleDetails() does not say much. It’s like calling a method doSomething(). Names like addAmountToTotal() or registerUser() or sumOfLineItems() may express intent.

    Your code contains three classes, but does not contain a class for the Cart. (I see you use a List<string> instead.)

    There is nowhere in your code a test that proves that if I buy A, A, B, A the total will be 150 (for instance).

    I see complex logic, with many IFs. It’s difficult to follow what the “previousItemCount += 1” do.

  10. Luca Minudel Says:

    for the last 3 points I agree that needs to be improved and I know how to do it.

    But for the first one … I tried hard but was not capable to come up with a better name. can you suggest me a better one ?

  11. matteo Says:

    How about WriteGrandTotalOn()
    or WriteGrandTotalTo() ?

  12. Luca Minudel Says:

    yep, good suggestions. thanks

  13. Luca Minudel Says:

    good suggestions. thank you.

  14. jacopo Says:

    Sorry for being so late..

    Here’s mine, not complete – just one test case – because I wanted to concentrate on pushing on refactoring:
    * http://pastie.org/1020661

    No “getters” at all:
    * price is calculated with anItem.priceGiven(priceRules)
    * total is sent to display with sum.displayResultOn(display)

  15. matteo Says:

    Jacopo:

    I like …displayResultOn(display). But:

    – why the Cart builds the rules with “new” instead of receiving them? That would force the Cart to, say, open a connection to the database if the rules are stored there.

    – anItem.priceGiven is a sort of getter

    – anItem.priceGiven breaks completely when you consider special offers

  16. jacopo Says:

    Hi matteo:
    1) short anwser is: there is not yet no tests for that. Having an external factory or using default constructor, so far, are equivalent
    2) not really: it encapsulates item’s “type”, which otherwise whould be exposed through a getter. priceRules stores rules, items know how to achieve prices for their “type”
    3) yes, you’re probably right!

    As I told in the previous comment, I just wanted to focus on your question about computing total using Cart and PriceRule collaborating, with no getter at all. (I’ve done this kata a few times, but wasn’t able to find anywhere on my laptop..)

    I’ll try going further and share the results with you,
    ciao.

  17. Gerardo Says:

    I agree with Matteo about point #2, priceGiven() is just a getter and nothing else, so the “you are forbidden to use getters” rule is broken.

    I encourage Jacopo and Luca to expose a more “OO Design” point of view before coding the answer.

    If no getters must be used, then you have to use “ByRef” style parameters in both Cart and PriceRules, because otherwise you won’t be able to know what items from the Cart are selected by the user, or what is the price from PriceRules of each item.

    So, my first approach is as follow:

    1. Cart must implement a method that receives an empty List of 3 Object references per item selected (Item, Quantity, and Subtotal), and this method will summarize the entire Cart resulting in a List as follow (for the example):

    Item: A, C, D
    Qty.: 3, 1, 1
    SubT: null, null, null

    2. Then, PriceRules must implement a method that receives the resulting List (if not empty) filling the third element for every item with the corresponding computed subtotal.

    3. This two functions must be called inside a third method implemented in a third class that handle the request for computation of the “total that the customer has to pay”, receiving the Cart and the PriceRules to use, and then iterate over the resulting List (completely filled) to sum every subtotal and then resolving how to show, return, store, or delegate the result handling to another class.

Leave a Reply