Reputation: 5237
I'm going through Strategy pattern listed out in https://en.wikipedia.org/wiki/Strategy_pattern and trying to understand how this would work when you want to use Dependency injection as well and make it easy for unit testing.
So the interface is:
interface BillingStrategy {
// Use a price in cents to avoid floating point round-off error
int getActPrice(int rawPrice);
}
There are two implementations:
@Component
NormalHours implements BillingStrategy {
public int getActPrice(int rawPrice) {
return rawPrice;
}
}
@Component
HappyHours implements BillingStrategy {
public int getActPrice(int rawPrice) {
return rawPrice/2;
}
}
Now there is a customer object for whom I want to keep track of how the total price:
class Customer {
private final List<Integer> drinks = new ArrayList<>();
private BillingStrategy strategy;
public Customer(BillingStrategy strategy) {
this.strategy = strategy;
}
public void add(int price, int quantity) {
this.drinks.add(this.strategy.getActPrice(price*quantity));
}
// Payment of bill
public int getBill() {
int sum = this.drinks.stream().mapToInt(v -> v).sum();
this.drinks.clear();
return sum;
}
// Set Strategy
public void setStrategy(BillingStrategy strategy) {
this.strategy = strategy;
}
}
Now say I've a file which I've which has information about purchases made during each hour of the day, and I need to compute the final bill for the customer.
@Component
public class Calculator {
public int calculate(File file) {
//pseudo code here
Customer customer = new Customer();
for(line in file) {
//parse price, strategy and quantity from line
customer.setStrategy(strategy);
customer.add(price, quantity);
}
return customer.getBill();
}
}
Obviously this doesn't work great for unit testing as a new object is created within the method and by using just regular Mockito it's going to be hard to mock the values returned for the strategies in Calculator class. Is there a different way to model the Customer & Calculator class to ensure ease of testing and use DI as much as possible?
I know one way is let the caller of Calculator pass in the object Customer, but however with the way the wiki has explained this particular Customer class we cannot make it as a Singleton and the same problem will exist.
Upvotes: 0
Views: 408
Reputation: 7521
Given strategy isn't related to the Customer itself and placing it inside Customer is DI violation. Instead just pass strategy as method argument
class Customer {
private final List<Integer> drinks = new ArrayList<>();
public void add(BillingStrategy strategy, int price, int quantity) {
drinks.add(strategy.getActPrice(price * quantity));
}
// Payment of bill
public int getBill() {
int sum = drinks.stream().mapToInt(v -> v).sum();
drinks.clear();
return sum;
}
}
@Component
public class Calculator {
public int calculate(Customer customer, File file) {
//pseudo code here
for(line in file) {
//parse price, strategy and quantity from line
customer.add(strategy, price, quantity);
}
return customer.getBill();
}
}
Upvotes: 1