HiveBrain v1.2.0
Get Started
← Back to all entries
patternjavaMinor

Shopping Cart design interview task

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
designinterviewcarttaskshopping

Problem

I was interviewed for one position and had the following task.

The goal is to design system according step 1, then adapt it to additional requirements in step 2.

Step 1: Shopping cart

  • You are building a checkout system for a shop which only sells apples and oranges.



  • Apples cost 60p and oranges cost 25p.



  • Build a checkout system which takes a list of items scanned at the till and outputs the total cost



  • For example: [ Apple, Apple, Orange, Apple ] => £2.05



  • Make reasonable assumptions about the inputs to your solution; for example, many candidates take a list of strings as input



Step 2: Simple offers

The shop decides to introduce two new offers:

  • buy one, get one free on Apples



  • 3 for the price of 2 on Oranges



Update your checkout functions accordingly

Please find my implementation below and let me know about weak points and what would you do differently or improve.

Checkout.java

import java.util.*;

public class Checkout {

private Set strategies = new HashSet<>();

public static Map PRICE_MAP = new HashMap<>();

static {
    PRICE_MAP.put("Orange", 0.25);
    PRICE_MAP.put("Apple", 0.6);
}

public Checkout() {
    this.strategies.add(new SummingStrategy());
}

public Checkout(CheckoutStrategy discountStrategy) {
    this();
    this.strategies.add(discountStrategy);
}

public double calculateTotal(List strings) {
    double[] result = new double[1];

    for (CheckoutStrategy strategy : strategies) {
        strategy.calculateTotal(strings, result);
    }

    return result[0];
}

}


CheckoutStrategy.java

import java.util.List;

public interface CheckoutStrategy {
    void calculateTotal(List strings, double[] result);
}


SummingStrategy.java

import java.util.List;
import static Checkout.PRICE_MAP;

public class SummingStrategy implements CheckoutStrategy {

@Override
public void calculateTotal(List items, double[] result) {
    result[0] = items.stream().mapToDouble(PRICE_MAP::get).sum();
  }

}


DiscountStrategy.j

Solution

I won't add to @mtj's comment (I totally agree that an array defined as a means to exchange information is a code smell).

I agree with @RobAu that you should stay far a away from those magic numbers and arrays. That would avoid bugs like:

this.counts[APPLE_INDEX] += other.getCounts()[ORANGE_INDEX];


Now to my comments.

Strategy is not the name of the Pattern applied here. It is more like a Composite Pattern, because several Strategies can be applied at the same time (although no method is available to add further discounts, but I suppose it was the intent).

In general I don't advise naming Objects by the Design Pattern they implement. Objects should be named as per what they do, not how you thought about making an architecture around them. It's useful in a tutorial, not in actual code. Carrying XXXStrategy everywhere gets real annoying, and lead to confusions (like a WallObserverListener : does it listen to an observer, or observe a listener?).

The solution to keep people to the page about you using a particular Pattern is Javadoc. This allows for cleaner code with method names that are more business-oriented, like:

public void apply(Discount discount);


Which is more obvious than:

public void addStrategy(CheckoutStrategy strategy);


A Better way to combine deals

You could use a Decorator pattern for your deals.

First clean up your interface:

public interface Checkout {
    double calculateTotal(List shoppingCart);
}


Then make a simple price calculator (Note there is no hard-coded pricing, it can be created at runtime:

public class SimpleCheckout implements Checkout {

    private final Map basePrice = new HashMap<>();

    public void addPrice(String item, double price) {
        basePrice.put(item, price);
    }

    @Override
    public double calculateTotal(List shoppingCart) {
        // Your own code, very good use of Streams, here
        return shoppingCart.stream().mapToDouble(basePrice::get).sum();
    }
}


Now we simply need to add discounts as a decoration layer:

public class DiscountValue implements Checkout {

    private final Checkout baseCheckout;
    private final String discountedItem;
    private final int minimumAmount;
    private final double discountValue;

    public DiscountValue(Checkout base, String item, int min, double discount) {
         this.baseCheckout = base;
         this.discountedItem = item;
         this.minimumAmount = min;
         this.discountValue = discount;
    }

    @Override
    public double calculateTotal(List shoppingCart) {
        int count = Collections.frequency(shoppingCart, discountedItem);
        // Integer division gives the number of times the discount is applied
        double deduction = (count / minimumAmount) * discountValue;
        return baseCheckout.calculateTotal(shoppingCart) - deduction;
    }
}


Now this is how you use it:

public static void main(String[] argc){
    Checkout pricing = new SimpleCheckout();
    pricing.addPrice("Apple", 0.25);
    pricing.addPrice("Orange", 0.6);
    pricing = new DiscountValue(pricing, "Apple", 2, 0.25); // I'm using a straight-up refund here
    pricing = new DiscountValue(pricing, "Orange", 3, 0.60); // I'm using a straight-up refund here
    List shoppingCart = new ArrayList<>();
    shoppingCart.add("Apple");
    shoppingCart.add("Orange");
    shoppingCart.add("Apple");
    shoppingCart.add("Apple");
    System.out.prinln(pricing.calculateTotal(shoppingCart));
}

Code Snippets

this.counts[APPLE_INDEX] += other.getCounts()[ORANGE_INDEX];
public void apply(Discount discount);
public void addStrategy(CheckoutStrategy strategy);
public interface Checkout {
    double calculateTotal(List<String> shoppingCart);
}
public class SimpleCheckout implements Checkout {

    private final Map<String, Double> basePrice = new HashMap<>();

    public void addPrice(String item, double price) {
        basePrice.put(item, price);
    }

    @Override
    public double calculateTotal(List<String> shoppingCart) {
        // Your own code, very good use of Streams, here
        return shoppingCart.stream().mapToDouble(basePrice::get).sum();
    }
}

Context

StackExchange Code Review Q#161134, answer score: 8

Revisions (0)

No revisions yet.