Reputation: 314
I have a doubt about a Object Oriented design. Let's say I have to implement a generic bill evaluater for phone calls. In input I can use a log of calls (number and duration in seconds) and I want get the price for each call.
What I thought is to use a generic Call class and an abstract BillEvaluator that, for each call, set the "bill strategy" IStragety, based on an abstract function. Each subclass (OrangeBill, VodafoneBill, ..) will have to implement the bill strategy based on some criteria (duration, number, ...). At the end, the real price is evaluated by class that implements IStragety.
Is this a good solution? If I want to try a new PhoneOperator, then I'll have to create a new subclass of BillHandler or if I could create a new IStrategy implementation. But if the Vodafone decided to add a new bill strategy (for instance, a call longer than 1h is free), then I will have to modify the VodafoneBill class... so my solution doesn't respect the SOLID principles!
Here is the code (avoided all the get/set and checks for simplicity):
import java.io.*;
import java.util.*;
// A generic call, with a number, duration in seconds
// and a price (to evaluate)
class Call{
public String number;
public int duration;
public double price;
public Call(String number, int duration){
this.number = number;
this.duration = duration;
}
public String toString(){
return number + " (" + duration + ")-->" + price;
}
}
// Strategy to determine the price of a call
interface IStrategy{
void setCost(Call call);
}
// Each seconds of the call is charged of 3 cents
class secondsPrice implements IStrategy{
public void setCost(Call call){
call.price = 0.03 * call.duration;
}
}
// each minutes of the conversation is charged 55 cents
// round the minutes value Es. 3:02 => 4 minutes.
class minutesPrice implements IStrategy{
public void setCost(Call call){
int duration = call.duration;
int minutes = duration / 60;
if(duration % 60 > 0){
minutes = minutes + 1;
}
call.price = 0.55 * minutes;
}
}
// each minutes of conversation is charged 1 cents.
class lowPrice implements IStrategy{
public void setCost(Call call){
call.price = 0.01 * (call.duration / 60);
}
}
// Generic class that set the price for each
// call in the log
abstract class BillHandler{
public void evaluateBill(List<Call> log){
for(Call aCall : log){
IStrategy s = billStrategy(aCall);
s.setCost(aCall);
}
}
abstract IStrategy billStrategy(Call call);
}
// Concrete implementation of the Orange Billing strategy.
class OrangeBill extends BillHandler{
IStrategy billStrategy(Call call){
if(call.duration <= 180){
return new secondsPrice();
}
else{
return new minutesPrice();
}
}
}
class VodafoneBill extends BillHandler{
IStrategy billStrategy(Call call){
if(call.number.equals("122")){
return new lowPrice();
}
else if(call.duration < 100){
return new secondsPrice();
}
else{
return new minutesPrice();
}
}
}
class myCode
{
public static void main (String[] args) throws java.lang.Exception
{
myCode c = new myCode();
List<Call> log = new ArrayList<>();
log.add(new Call("122", 180));
log.add(new Call("114", 179));
log.add(new Call("122", 200));
log.add(new Call("411", 54));
System.out.println(log);
BillHandler bill = new OrangeBill();
bill.evaluateBill(log);
System.out.println("OrangeBill:");
System.out.println(log);
bill = new VodafoneBill();
bill.evaluateBill(log);
System.out.println("VodafoneBill:");
System.out.println(log);
}
}
Possible implementation using Chain of responsibility pattern
import java.io.*;
import java.util.*;
// A generic call, with a number, duration in seconds
// and a price (to evaluate)
class Call{
public String number;
public int duration;
public double price;
public Call(String number, int duration){
this.number = number;
this.duration = duration;
}
public String toString(){
return number + " (" + duration + ")-->" + price;
}
}
// Interface implemented by different Provider
interface BillHandler{
void priceCall(Call call);
}
//Orange provider
class OrangeBill implements BillHandler{
private callHandler secondsH = new orangeSecondsHandler();
private callHandler minutesH = new orangeMinutesHandler();
private callHandler commondH = new commonFareHandler();
public void priceCall(Call call){
secondsH.processCall(call);
}
OrangeBill(){
secondsH.setSuccessor(minutesH);
minutesH.setSuccessor(commondH);
}
}
// Vodafone provider
class VodafoneBill implements BillHandler{
private callHandler secondsH = new vodafoneSecondsHandler();
private callHandler minutesH = new vodafoneMinutesHandler();
private callHandler lowCallH = new vodafoneLowCallHandler();
private callHandler commondH = new commonFareHandler();
public void priceCall(Call call){
secondsH.processCall(call);
}
VodafoneBill(){
lowCallH.setSuccessor(secondsH);
secondsH.setSuccessor(minutesH);
minutesH.setSuccessor(commondH);
}
}
// Generic call handler
abstract class callHandler{
public callHandler next;
public void setSuccessor(callHandler next){
this.next = next;
}
abstract public boolean isChargable(Call call);
abstract public void priceCall(Call call);
public void processCall(Call call){
if(isChargable(call)){
priceCall(call);
}
else{
next.processCall(call);
}
}
}
// Concrete implementations of different call handler based
// on its provider policy
// Each seconds of the call is charged of 3 cents
class orangeSecondsHandler extends callHandler{
public boolean isChargable(Call call){
return call.duration <= 180;
}
public void priceCall(Call call){
call.price = 0.03 * call.duration;
}
}
// each minutes of the conversation is charged 55 cents
// round the minutes value Es. 3:02 => 4 minutes.
class orangeMinutesHandler extends callHandler{
public boolean isChargable(Call call){
return call.duration <= 180;
}
public void priceCall(Call call){
int duration = call.duration;
int minutes = duration / 60;
if(duration % 60 > 0){
minutes = minutes + 1;
}
call.price = 0.55 * minutes;
}
}
// Each seconds of the call is charged of 5 cents
class vodafoneSecondsHandler extends callHandler{
public boolean isChargable(Call call){
return call.duration <= 100;
}
public void priceCall(Call call){
call.price = 0.05 * call.duration;
}
}
// each minutes of the conversation is charged 30 cents
// round the minutes value Es. 3:02 => 4 minutes.
class vodafoneMinutesHandler extends callHandler{
public boolean isChargable(Call call){
return call.duration <= 250;
}
public void priceCall(Call call){
int duration = call.duration;
int minutes = duration / 60;
if(duration % 60 > 0){
minutes = minutes + 1;
}
call.price = 0.30 * minutes;
}
}
// Call to selected number are charged by 0.02 cents
// for every minute, without round!
class vodafoneLowCallHandler extends callHandler{
public boolean isChargable(Call call){
return call.number.equals("122");
}
public void priceCall(Call call){
int duration = call.duration;
int minutes = duration / 60;
call.price = 0.02 * minutes;
}
}
class commonFareHandler extends callHandler{
public boolean isChargable(Call call){
return true;
}
public void priceCall(Call call){
call.price = 1 * call.duration;
}
}
class myCode
{
public static void main (String[] args) throws java.lang.Exception
{
myCode c = new myCode();
List<Call> log = new ArrayList<>();
log.add(new Call("122", 180));
log.add(new Call("114", 179));
log.add(new Call("122", 200));
log.add(new Call("411", 54));
System.out.println(log);
// Evaluate the bill with Orange
BillHandler bill = new OrangeBill();
for(Call call : log)
bill.priceCall(call);
System.out.println("OrangeBill:");
System.out.println(log);
// Evaluate the bill with Vodafone
bill = new VodafoneBill();
for(Call call : log)
bill.priceCall(call);
System.out.println("VodafoneBill:");
System.out.println(log);
}
}
Upvotes: 1
Views: 293
Reputation: 5659
I can suggest the following:
IStrategy
seems redundant to me. The probability of one strategy being applicable to more than one operator seems low. So, if there is a 1:1 mapping of strategy to operator, I'd cut out the extra interface.BillHandler
s, because that will allow you to get rid of the multiple if-else conditions on Call
. Look at this example.Upvotes: 1