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<CheckoutStrategy> strategies = new HashSet<>();
public static Map<String, Double> 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<String> 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<String> strings, double[] result);
}
SummingStrategy.java
import java.util.List;
import static Checkout.PRICE_MAP;
public class SummingStrategy implements CheckoutStrategy {
@Override
public void calculateTotal(List<String> items, double[] result) {
result[0] = items.stream().mapToDouble(PRICE_MAP::get).sum();
}
}
DiscountStrategy.java
import Checkout;
import java.util.List;
public class DiscountStrategy implements CheckoutStrategy {
@Override
public void calculateTotal(List<String> items, double[] result) {
ItemCounter counter = items.stream().collect(ItemCounter::new, ItemCounter::accept, ItemCounter::combine);
double appleDiscount = (counter.counts[ItemCounter.APPLE_INDEX] / 2) * Checkout.PRICE_MAP.get("Apple");
double orangeDiscount = (counter.counts[ItemCounter.ORANGE_INDEX] / 3) * Checkout.PRICE_MAP.get("Orange");
result[0] -= orangeDiscount;
result[0] -= appleDiscount;
}
class ItemCounter {
static final int APPLE_INDEX = 0;
static final int ORANGE_INDEX = 1;
private int[] counts = new int[2];
public void accept(String item) {
switch (item) {
case "Apple":
counts[APPLE_INDEX]++;
break;
case "Orange":
counts[ORANGE_INDEX]++;
break;
}
}
public void combine(ItemCounter other) {
this.counts[APPLE_INDEX] += other.getCounts()[APPLE_INDEX];
this.counts[APPLE_INDEX] += other.getCounts()[ORANGE_INDEX];
}
public int[] getCounts() {
return counts;
}
}
}