mardi 10 octobre 2017

How to be sure that your code follows the SOLID principle?

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);

    }
}

Aucun commentaire:

Enregistrer un commentaire