Factory Method Design Pattern

Factory Method design pattern is a creational pattern which defines an interface for creating an object, but let subclasses decide which class to instantiate. Factory Method lets a class defer instantiation to subclasses.

Let’s see an example of payment system where we can pay with different types of payment methods. We will create a payment service and delegate its instantiation logic to Factories. Our project structure will look like this.

├── composer.json
├── composer.lock
├── src
│   ├── Factories
│   │   └── Payment
│   │       ├── PaymentFactoryInterface.php
│   │       ├── PaypalFactory.php
│   │       └── StripeFactory.php
│   └── Services
│       └── Payment
│           ├── PaymentInterface.php
│           ├── Paypal.php
│           └── Stripe.php
├── tests
│   └── PaymentTest.php
└── vendor

Create PaymentInterface first.

<?php declare(strict_types=1);

namespace App\Services\Payment;

interface PaymentInterface
{
    public function pay(): string;
}
    
?>

Paypal service will implement the PaymentInterface

<?php declare(strict_types=1);

namespace App\Services\Payment;
use App\Services\Payment\PaymentInterface;

class Paypal implements PaymentInterface
{
    public function pay(): string
    {
        // process payment via paypal
        return 'SUCCESS';
    }
}
?>

Similarly Sripe service will implement PaymentInterface.

<?php declare(strict_types=1);

namespace App\Services\Payment;
use App\Services\Payment\PaymentInterface;

class Stripe implements PaymentInterface
{
    public function pay(): string
    {
        // process payment via stripe
        return "SUCCESS";
    }
}
?>

Any other payment service in future if you need should also implement this PaymentInterface. Now, we will create factories.

PaymentFactoryInterface.php

<?php declare(strict_types=1);

namespace App\Factories\Payment;

use App\Services\Payment\PaymentInterface;

interface PaymentFactoryInterface
{
    public function getInstance(): PaymentInterface;
}

?>

PaypalFactory.php

<?php declare(strict_types=1);

namespace App\Factories\Payment;

use App\Services\Payment\Paypal;
use App\Services\Payment\PaymentInterface;
use App\Factories\Payment\PaymentFactoryInterface;


class PaypalFactory implements PaymentFactoryInterface
{
    public function getInstance(): PaymentInterface
    {
        return new Paypal();
    }
}

?>

StripeFactory.php

<?php declare(strict_types=1);

namespace App\Factories\Payment;

use App\Services\Payment\Stripe;
use App\Services\Payment\PaymentInterface;
use App\Factories\Payment\PaymentFactoryInterface;

class StripeFactory implements PaymentFactoryInterface
{
    public function getInstance(): PaymentInterface
    {
        return new Stripe();
    }
}

?>

Ok. we are done with the service and factories for the payment. Now write phpunit test cases to test the factory method. We will not directly instantiate the services, rather we should use the dedicated factories for the service. Factory will decide which concrete classes may I need to create class object.

<?php declare(strict_types=1);
namespace App\Tests;

require_once './vendor/autoload.php'; 
use App\Factories\Payment\PaypalFactory;
use App\Factories\Payment\StripeFactory;
use App\Services\Payment\Paypal;
use App\Services\Payment\Stripe;
use PHPUnit\Framework\TestCase;

class PaymentTest extends TestCase
{
    public function testCanCreatePaypalInstance()
    {
        $paymentFactory = new PaypalFactory();
        $payment = $paymentFactory->getInstance();

        $this->assertInstanceOf(Paypal::class, $payment);
    }

    public function testCanCreateStripeInstance()
    {
        $paymentFactory = new StripeFactory();
        $payment = $paymentFactory->getInstance();

        $this->assertInstanceOf(Stripe::class, $payment);
    }

    public function testCanPayWithPaypal()
    {
        $paymentFactory = new PaypalFactory();
        $payment = $paymentFactory->getInstance();
        $response = $payment->pay();
        $this->assertEquals($response, 'SUCCESS');
    }

    public function testCanPayWithStripe()
    {
        $paymentFactory = new StripeFactory();
        $payment = $paymentFactory->getInstance();
        $response = $payment->pay();
        $this->assertEquals($response, 'SUCCESS');
    }
}

Execute tests

$./vendor/bin/phpunit tests
PHPUnit 7.5.20 by Sebastian Bergmann and contributors.

....                                                                4 / 4 (100%)

Time: 33 ms, Memory: 4.00 MB

OK (4 tests, 4 assertions)
Written on March 26, 2021