Object-oriented – How to use SOLID principles in this example

design-patternsobject-orientedPHPsolidweb-applications

I have reading about SOLID principles last week and decided to try to solve real world problem by using them. So I got the problem of real world ecommerce site managing totals.

Problem Statement:
We have to calculate some totals like subtotal, taxes, vouchers, coupon, total on a cart products.

So I have decided to make TotalManager which returns total_data and total to the rest of the program.

// total data variable looks like
array('key'=>value)

e.g. array('subtotal'=>100,'total'=>150,'tax'=>50)

    class TotalManager{
        private $total_data;
        public function get_total_data()
        {
            $totals = array('subtotal','total','taxes')         
            // foreach totals array gets each object check its instance of interface itotal and called it's get method.
        }        
    }

    Interface Itotal
    {
        public function get_total_data();
        public function get_total();
    }    

    class subtotal implements Itotal
    {
        public function get_total_data()
        {
            // it needs cart object
            // get the cart product calculate totals and return total_data and total 
        }
        public function get_total()
        {
            return the subtotal in double/float
        }
    }

    class total implements Itotal
    {
        public function get_total_data()
        {
            // get the other totals object  and calls it get_total method
            // calulate sum of all totals and return 
        }

        public function get_total()
        {
            // calculate total and return back;
        }
    }

But I am facing problems in this model:

For dependency inversion principle I cannot directly create the object of cart or subtotal in subtotal, Total class respectively I have to pass them from controller at total manager.

When I think about that whenever I am adding the class I have to change the Total manager for adding the new method so it's violating open/closed principle. How to solve this in OOD?

Best Answer

I see you use php, try to stick to the psr coding styles: psr-1 and psr-2.


Look into the decorator pattern. this is a pattern commonly used in this kind of situations.


You probably have some kind of LineItem class that is used to represent items in your cart. They probably look something like this:

interface LineItem
{
    public function getPrice();
}

With then an implementation for your product:

class ProductLineItem implements LineItem
{
    public function __construct(Product $product, $quantity) {
        $this->product = $product;
        $this->quantity = $quantity;
    }

    public function getPrice()
    {
        return $this->quantity * $this->product->getPrice();
    }
}

Now it is time to create our abstract decorator:

abstract class LineItemDecorator implements LineItem
{
    public function __construct(LineItem $item) {
        $this->item = $item;
    }

    public function getPrice()
    {
        return $this->item->getPrice(); //delegation
    }
}

this abstract class is not needed. I just tend to use it to delegate everything to the base object only in one place. Especially useful if multiple methods exist.

Note also how my Decorator implements the same interface!

And then now it's time for our Discounts:

class DiscountLineITemDecorator extends LineItemDecorator 
{
    public function getPrice()
    {
        return $this->item->getPrice() * 0.75;
    }
}

We would then procede to use it:

$lineItem = new ProductLineItem($theProduct, 2);
$lineItemWithDiscount= new DiscountLineITemDecorator($lineItem);
//lets add it to our cart
$cart->addLineItem($lineItemWithDiscount);

Our cart then probably also has a getPrice() method. Probably it will look something like this:

foreach ($this->lineITems as $lineItem) {
    $price += $lineItem->getPrice();
}
return $price;

So what about discount on an entire cart?

$cartWithDiscount = new DiscountCartDecorator($cart);

and boom

$cartWithDiscount->getPrice();

All code used here serves as an example. The naming can be improved a lot, so feel free to edit!

Related Topic