Creational Design Patterns

Giới thiệu

Xin chào, ở Phần 1 thì mình đã giới thiệu tổng quát về design pattern, trong phần này chúng ta sẽ tìm hiểu về Creational Design Patterns nhé.

Link bài viết:

Nội dung

Simple factory

Thực tế: Giải dụ, bạn đang xây dựng một ngôi nhà và bạn cần cửa ra vào. Bạn có thể mặc quần áo thợ mộc, mang theo một ít gỗ, keo dán, đinh và tất cả các công cụ cần thiết và bắt đầu xây dựng nó hoặc là bạn có thể chỉ cần gọi cho nhà máy và giao cửa cho bạn để bạn không cần phải học bất cứ điều gì về việc làm cửa hoặc phải đối phó với mớ hỗn độn đi kèm.

Nói một cách dễ hiểu: Simple factory đơn giản là tạo ra một intance cho client mà không để lộ bất kì logic khởi tạo nào cho client biết.

Trong lập trình hướng đối tượng (OOP), một factory là một object dùng để tạo các object khác, bạn cũng có thể hiểu là factory là một function hoặc method trả về object từ một method được gọi tới, đó là new.

Ví dụ

Đầu tiên chúng ta sẽ có interface cho Door và implement nó cho WoodenDoor.

interface Door
{
    public function getWidth(): float;
    public function getHeight(): float;
}

class WoodenDoor implements Door
{
    protected $width;
    protected $height;

    public function __construct(float $width, float $height)
    {
        $this->width = $width;
        $this->height = $height;
    }

    public function getWidth(): float
    {
        return $this->width;
    }

    public function getHeight(): float
    {
        return $this->height;
    }
}

Sau đó chúng ta tạo DoorFactory để tạo ra cái cửa và trả nó về cho người mua nhé.

class DoorFactory
{
    public static function makeDoor($width, $height): Door
    {
        return new WoodenDoor($width, $height);
    }
}

Và bây giờ nó có thể được sử dụng như sau:

// bán cho anh cửa có kích thước là 100x200 :)))
$door = DoorFactory::makeDoor(100, 200);

// kiểm tra cửa có đúng kích thước yêu cầu không
echo 'Width: ' . $door->getWidth();
echo 'Height: ' . $door->getHeight();

// nhìn ổn đấy, bán cho anh thêm 1 cái nữa với kích thước 50x100
$door2 = DoorFactory::makeDoor(50, 100);

Khi nào nên sử dụng ?

Khi tạo một object với logic chung, xử lý nó ở fatory và chỉ việc gọi ra khi cần, tránh lặp lại code ở mọi nơi.

Factory Method

Thực tế: Giả sử trong trường hợp của một người quản lý nhà tuyển dụng. Một người không thể phỏng vấn cho tất cả các vị trí. Dựa trên công việc cần tuyển, người này phải ủy thác cho những người khác có kinh nghiệm trong việc này.

Nói một cách dễ hiểu: Thì từ đó bạn có thể hiểu là nó cung cấp một cách để ủy thác logic khởi tạo cho class con.

Trong lập trình dựa trên lớp, factory method pattern sử dụng các phương thức của factory để giải quyết vấn đề tạo object mà không chỉ định class chính xác của đối tượng sẽ được tạo. Điều này được thực hiện bằng cách tạo các đối tượng bằng cách gọi một phương thức được khai báo trong một interface và implement bởi class con, hoặc implement trong base class và được ghi đè tùy ý thay vì gọi một hàm constructor.

Ví dụ

Lấy ví dụ quản lý tuyển dụng ở trên. Trước hết chúng ta có một interface là interviewer và một số implement cho nó

interface Interviewer
{
    public function askQuestions();
}

class Developer implements Interviewer
{
    public function askQuestions()
    {
        echo 'Asking about design patterns!';
    }
}

class CommunityExecutive implements Interviewer
{
    public function askQuestions()
    {
        echo 'Asking about community building';
    }

Bây giờ chúng ta sẽ tạo HiringManager:

abstract class HiringManager
{

    // Factory method
    abstract protected function makeInterviewer(): Interviewer;

    public function takeInterview()
    {
        $interviewer = $this->makeInterviewer();
        $interviewer->askQuestions();
    }
}

Bây giờ bất kì lớp con nào extend HiringManager đều được cung cấp người phỏng vấn cần thiết:

class DevelopmentManager extends HiringManager
{
    protected function makeInterviewer(): Interviewer
    {
        return new Developer();
    }
}

class MarketingManager extends HiringManager
{
    protected function makeInterviewer(): Interviewer
    {
        return new CommunityExecutive();
    }
}

Và sau đó nó có thể được sử dụng bằng cách:

$devManager = new DevelopmentManager();
$devManager->takeInterview(); // Output: Asking about design patterns

$marketingManager = new MarketingManager();
$marketingManager->takeInterview(); // Output: Asking about community building.

Khi nào sử dụng ?

Useful when there is some generic processing in a class but the required sub-class is dynamically decided at runtime. Or putting it in other words, when the client doesn't know what exact sub-class it might need.

Abstract Factory

Thực tế: Từ ví dụ tạo ra cái cửa ở trên từ Simple factory. Dựa trên những gì ta cần, chúng ta có thể có được cánh cửa gỗ, hoặc sắt, hoặc nhựa, ... từ cửa hàng. Ngoài ra có thể chúng ta sẽ cần một người thợ chuyên lắp cửa, thợ mộc cho cửa gỗ, thợ hàn cho cửa sắt, ... Như bạn thấy thì đó là một sự phụ thuộc giữa các loại cửa với người thợ.

Nói một cách dễ hiểu: Một factory của factories, một facotry nhóm các factory riêng lẻ nhưng có liên quan, phụ thuộc đến nhau mà không chỉ định class cụ thể của chúng.

Abtract factory pattern cung cấp một cách để gói gọn một nhóm các factory riêng lẻ có liên quan mà không cần khai báo các class cụ thể của chúng

Ví dụ

Vẫn từ ví dụ cánh cửa ở trên, trước hết chúng ta có Door Inerview và một vài implementation cho nó

interface Door
{
    public function getDescription();
}

class WoodenDoor implements Door
{
    public function getDescription()
    {
        echo 'I am a wooden door';
    }
}

class IronDoor implements Door
{
    public function getDescription()
    {
        echo 'I am an iron door';
    }
}

Sau đó chúng ta cần có thợ phù hợp cho từng loại cửa

interface DoorFittingExpert
{
    public function getDescription();
}

class Welder implements DoorFittingExpert
{
    public function getDescription()
    {
        echo 'I can only fit iron doors';
    }
}

class Carpenter implements DoorFittingExpert
{
    public function getDescription()
    {
        echo 'I can only fit wooden doors';
    }
}

bây giờ chúng ta đã có abtract factory, nó sẽ cho phép tạo ra các đối tượng liên quan, tức là nhà máy cửa gỗ sẽ tạo ra một cánh cửa gỗ và chuyên lắp cửa gỗ, tương tự với cửa sắt.

interface DoorFactory
{
    public function makeDoor(): Door;
    public function makeFittingExpert(): DoorFittingExpert;
}

// Wooden factory to return carpenter and wooden door
class WoodenDoorFactory implements DoorFactory
{
    public function makeDoor(): Door
    {
        return new WoodenDoor();
    }

    public function makeFittingExpert(): DoorFittingExpert
    {
        return new Carpenter();
    }
}

// Iron door factory to get iron door and the relevant fitting expert
class IronDoorFactory implements DoorFactory
{
    public function makeDoor(): Door
    {
        return new IronDoor();
    }

    public function makeFittingExpert(): DoorFittingExpert
    {
        return new Welder();
    }
}

Và sau đó nó có thể sử dụng bằng cách:

$woodenFactory = new WoodenDoorFactory();

$door = $woodenFactory->makeDoor();
$expert = $woodenFactory->makeFittingExpert();

$door->getDescription();  // Output: I am a wooden door
$expert->getDescription(); // Output: I can only fit wooden doors

// Same for Iron Factory
$ironFactory = new IronDoorFactory();

$door = $ironFactory->makeDoor();
$expert = $ironFactory->makeFittingExpert();

$door->getDescription();  // Output: I am an iron door
$expert->getDescription(); // Output: I can only fit iron doors

Như bạn có thể thấy nhà máy cửa gỗ đã đóng gói thợ mộc và cửa gỗ, nhà máy cửa sắt đã đóng gói cửa sắt và thợ hàn. Và do đó, nó đã giúp chúng ta đảm bảo rằng với mỗi cánh cửa được tạo ra đều ổn.

Khi nào nên dùng ?

Khi có sự phục thuộc liên quan đến logic sáng tạo.

Builder

Nói một cách dễ hiểu: Nó sẽ hữu ích khi có nhiều bước liên quan khi tạo ra một object

Ví dụ

public function __construct($size, $cheese = true, $pepperoni = true, $tomato = false, $lettuce = true)
{
}

Như bạn thấy thì ố lượng tham số truyền vào contructor dễ bị mất kiểm soát, khó hiểu, cộng thêm việc có thể sẽ tăng các tham số nếu như bạn muốn thêm các tùy chọn khác trong tương lai.

Giải pháp là sử dụng builder pattern. Trước hết chúng ta sẽ có một cái Burger:

class Burger
{
    protected $size;

    protected $cheese = false;
    protected $pepperoni = false;
    protected $lettuce = false;
    protected $tomato = false;

    public function __construct(BurgerBuilder $builder)
    {
        $this->size = $builder->size;
        $this->cheese = $builder->cheese;
        $this->pepperoni = $builder->pepperoni;
        $this->lettuce = $builder->lettuce;
        $this->tomato = $builder->tomato;
    }
}

// và đây là builder
class BurgerBuilder
{
    public $size;

    public $cheese = false;
    public $pepperoni = false;
    public $lettuce = false;
    public $tomato = false;

    public function __construct(int $size)
    {
        $this->size = $size;
    }

    public function addPepperoni()
    {
        $this->pepperoni = true;
        return $this;
    }

    public function addLettuce()
    {
        $this->lettuce = true;
        return $this;
    }

    public function addCheese()
    {
        $this->cheese = true;
        return $this;
    }

    public function addTomato()
    {
        $this->tomato = true;
        return $this;
    }

    public function build(): Burger
    {
        return new Burger($this);
    }
}

Sau đó, chúng ta có thể dễ dàng sử dụng:

$burger = (new BurgerBuilder(14))
                    ->addPepperoni()
                    ->addLettuce()
                    ->addTomato()
                    ->build();

Khi nào cần dùng ?

Khi có nhiều thứ cần truyền vào contructor, sự khác biệt từ factory pattern là ở chỗ này. factory pattern sẽ được sử dụng khi tạo là quy trình một bước, trong khi builder pattern sẽ được sử dụng khi tạo là quy trình nhiều bước

Prototype

Thực tế: Bạn có nhớ cừu Dolly chứ ? nó được nhân bản. không biết chi tiết nó ra sao nhưng mấu chốt là nó được nhân bản.

Nói một cách dễ hiểu: Tạo một đối tượng dựa trên đối tượng thông qua nhân bản

Wiki: The prototype pattern is a creational design pattern in software development. It is used when the type of objects to create is determined by a prototypical instance, which is cloned to produce new objects.

Tóm lại, nó cho phép bạn tạo ra bản sao của object và sửa đổi nó theo nhu cầu, thay vì phải thực hiện lại các thao tác thiết lập ban đầu.

Ví dụ

class Sheep
{
    protected $name;
    protected $category;

    public function __construct(string $name, string $category = 'Mountain Sheep')
    {
        $this->name = $name;
        $this->category = $category;
    }

    public function setName(string $name)
    {
        $this->name = $name;
    }

    public function getName()
    {
        return $this->name;
    }

    public function setCategory(string $category)
    {
        $this->category = $category;
    }

    public function getCategory()
    {
        return $this->category;
    }
}

Clone bằng cách:

$original = new Sheep('Jolly');
echo $original->getName(); // Jolly
echo $original->getCategory(); // Mountain Sheep

// Clone and modify what is required
$cloned = clone $original;
$cloned->setName('Dolly');
echo $cloned->getName(); // Dolly
echo $cloned->getCategory(); // Mountain sheep

Singleton

Thực tế: Một nước chỉ có 1 tổng thống, và đó là Singleton 😃)

Nói dễ hiểu: Đảm bảo rằng chỉ có một đối tượng của một lớp cụ thể được tạo.

Wiki: In software engineering, the singleton pattern is a software design pattern that restricts the instantiation of a class to one object. This is useful when exactly one object is needed to coordinate actions across the system.

Singleton pattern thực sự là anti-pattern và tránh lạm dụng nó. Không hẳn là xấu và sẽ có trưởng hợp cần đến nó nhưng nên thận trọng khi sử dụng nó vì nó sẽ là global state, và thay đổi nó ở một nơi thì có thể ảnh hướng đến những cái liên quan, việc debug trở nên khó khăn, thêm nữa là nó làm cho code được liên kết chặt chẽ + mocking singleton trở nên khó khăn

Ví dụ

Để tạo một singleton, tạo ra contructor private, disable cloning, disable phần mở rộng và tạo biến static để chứa intance.

final class President
{
    private static $instance;

    private function __construct()
    {
        // Hide the constructor
    }

    public static function getInstance(): President
    {
        if (!self::$instance) {
            self::$instance = new self();
        }

        return self::$instance;
    }

    private function __clone()
    {
        // Disable cloning
    }

    private function __wakeup()
    {
        // Disable unserialize
    }
}

// sử dụng
$president1 = President::getInstance();
$president2 = President::getInstance();

var_dump($president1 === $president2); // true

Tổng kết

Cảm ơn bạn đã đọc hết bài viết, nhớ đọc hết nha :v. Happy coding !!! ❤️ ❤️


All Rights Reserved