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(); } } ?>
Test case:
<?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)