user1692342
user1692342

Reputation: 5237

Using strategy pattern along with Dependency injection

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

Answers (1)

Nikolai  Shevchenko
Nikolai Shevchenko

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

Related Questions