1. The problem

Imagine your eCommerce website, for the very first time only selling physical products. and you write all of the logic in the Product Class. Then a few months after that, the business decided to sell virtual products. Now you realize that you may have to create VirtualProduct. Adding this is a big deal since it may require change for the whole codebase. And, even if you did it successfully, what if in the future the business also sell CBD product?

2. The solution

The factory method is a creational pattern that focuses on creating objects. We can use it from a very early time to get rid of the problem above.
So the idea is you have a Client that does some operations, and the core of the operation should be switchable by the class that contains it.

The main point is the factoryMethod(), that return type is the Product interface.

3. The rules

  • All target objects must follow an interface
  • A Creator declares factory method (abstract), then child class extend and implement the abstract method

4. The Implementation 

First, Tax calculator class must have an abstract method, which is the identification of this pattern: fatoryMethod:

<?php

abstract class TaxCalculator
{
    abstract protected function factoryMethod() : Product;

    public function calculate()
    {
        // handle calculating logic here:
        // ....
        return  $this->factoryMethod()->getTaxRate();
    }
}

class PhysicalGoodsTaxCalculator extends TaxCalculator
{
    protected function factoryMethod() : Product 
    {
        return new PhysicalProduct();
    }
}

class VirtualGoodsTaxCalculator extends TaxCalculator
{
    protected function factoryMethod() : Product 
    {
        return new VirtualProduct();
    }
}

class CBDGoodsTaxCalculator extends TaxCalculator
{
    protected function factoryMethod() : Product 
    {
        return new CBDProduct();
    }
}	

The client will use PhysicalGoodsTaxCalculator or VirtualGoodsTaxCalculator, CBDGoodsTaxCalculator to calculate tax fee. As you can see, for each type of product, we have a corresponding object for it, which is PhysicalProduct, VirtualProduct, and CBDProduct.

interface Product
{
    public function getTaxRate();
}

class PhysicalProduct implements Product
{
    public function getTaxRate()
    {
        return 0.05;
    }
}

class VirtualProduct implements Product
{
    public function getTaxRate()
    {
        return 0.02;
    }
}

class CBDProduct implements Product
{
    public function getTaxRate()
    {
        return 0.12;
    }
}

Now, client may do something like this

function clientCalculate(TaxCalculator $taxCalculator) 
{
    $orderTotal = 999;
    return $orderTotal + $orderTotal * $taxCalculator->calculate();
}

echo clientCalculate(new PhysicalGoodsTaxCalculator());

And the result:

The main purpose here is in the future if we have a new product type with a different Tax rate, we won’t have to break the existing code.

5. The conclusion

Factory method handles 2/5 SOLID attribute:

  • Single Responsibility Principle
  • Open/Closed Principle.

In addition, you may want to take a look at Strategy pattern


Leave a Reply

Your email address will not be published. Required fields are marked *