Facade design pattern :(

https://viblo.asia/p/tap-8-facade-laravel-bWrZnwnrlxw

Xem thêm: https://allaravel.com/blog/facade-pattern-don-gian-hoa-trong-viet-code-php

III. Laravel facade hoạt động như thế nào? (How Laravel facade work)

Trước tiên ta hãy cùng tìm hiểu một chút về facade design pattern.

1. Facade design pattern

Về khái niệm, facade design pattern hiểu đơn giản là nó sẽ giúp coder giao tiếp với source code xử lý phức tạp một cách dễ dàng thông qua một class facade trung gian.

Giả sử mình có thông tin của một user bao gồm tên, địa chỉ và học vấn. Trong đó:

  • Tên gồm: họ tên và tên đăng nhập

  • Địa chỉ: số nhà, đường, thành phố

  • Học vấn: trường cấp 1, 2, 3 và đại học

Đây là file data của user đó:

<?php

return [
    'name' => [
        'fullname' => 'Nguyễn Văn A',
        'username' => 'anguyen'
    ],
    
    'address' => [
        'number_house' => '89',
        'street' => 'Lê Lợi',
        'city' => 'Hồ Chí Minh'
    ],
    
    'education' => [
        'primary_school' => 'Nguyễn Trãi',
        'junior_high_school' => 'Lê Hồng Phong',
        'high_school' => 'Lê Lợi',
        'university' => 'Sài Gòn'
    ]
];

Yêu cầu: Lấy tất cả thông tin user.

Nếu làm theo cách thông thường thì bạn sẽ có thể xử lý theo hai hướng:

  1. Xử lý tất cả các yêu cầu trên trong một class User.

  2. Tạo ra 3 class con NameUser, AddressUserEducationUser để xử lý từng yêu cầu.

Đối với cách 1, mình sẽ tạo thêm file User.php, ta sẽ có cấu trúc thư mục như sau:

myproject/
|   autoload.php
|   data.php
|   User.php
|   index.php

Các bạn quan sát nội dung file User.php mình code để thực hiện yêu cầu trên:

<?php

class User
{
    protected $user, $name, $address, $education;
    
    public function __construct()
    {
         $this->user = require_once 'data.php';
         
         $this->name = $this->user['name'];
         $this->address = $this->user['address'];
         $this->education = $this->user['education'];
    }
    
    public function getFullname()
    {
        return $this->name['fullname'];
    }
    
    public function getUsername()
    {
        return $this->name['username'];
    }
    
    public function getNumberHouse()
    {
        return $this->address['number_house'];
    }
    
    public function getStreet()
    {
        return $this->address['street'];
    }
    
    public function getCity()
    {
        return $this->address['city'];
    }
    
    public function getPrimarySchool()
    {
        return $this->education['primary_school'];
    }
    
    public function getJuniorHighSchool()
    {
        return $this->education['junior_high_school'];
    }
    
    public function getHighSchool()
    {
        return $this->education['high_school'];
    }
    
    public function getUniversity()
    {
        return $this->education['university'];
    }
}

Đầu tiên ta nói về ưu điểm của cách 1. Nếu xử lý tất cả trong một class User thì sẽ giúp cho coder dễ dàng lấy thông tin user một cách nhanh chóng.

<?php

require_once 'autoload.php';

$user = new User;

$user->getFullname();
$user->getCity();

//

Nhưng xét về nhược điểm, bạn có thể nhận ra rằng file User.php quá dài, xử lý nhiều trường khác nhau. Nếu đây không phải là một ví dụ nhỏ dễ hình dung mà là một project quy mô vừa thôi thì cũng đủ làm ta thấy choáng ngợp nếu nhồi nhét các phương thức, thuộc tính vào một class như vậy.

Nhận xét cách 1:

  • Ưu điểm: cung cấp cú pháp dễ nhớ, nhanh chóng

  • Nhược điểm: class xử lý quá nhiều, khó bảo trì, nâng cấp.

⇒ Chưa thuyết phục.

Đối với cách 2, ta sẽ có cấu trúc thư mục như sau:

myproject/
|   autoload.php
|   data.php
|   NameUser.php
|   AddressUser.php
|   EducationUser.php
|   index.php

Nội dung mỗi class NameUser.php, AddressUser.phpEducationUser.php lần lượt là:

<?php

class NameUser
{
    protected $name;
    
    public function __construct()
    {
        $data = require_once 'data.php';
        
        $this->name = $data['name'];
    }
    
    public function getFullname()
    {
        return $this->name['fullname'];
    }
    
    public function getUsername()
    {
        return $this->name['username'];
    }
}
<?php

class AddressUser
{
    protected $address;
    
    public function __construct()
    {
        $data = require_once 'data.php';
        
        $this->name = $data['address'];
    }
    
    public function getNumberHouse()
    {
        return $this->address['number_house'];
    }
    
    public function getStreet()
    {
        return $this->address['street'];
    }
    
    public function getCity()
    {
        return $this->address['city'];
    }
}
<?php

class EducationUser
{
    protected $education;
    
    public function __construct()
    {
        $data = require_once 'data.php';
        
        $this->name = $data['address'];
    }
    
    public function getPrimarySchool()
    {
        return $this->education['primary_school'];
    }
    
    public function getJuniorHighSchool()
    {
        return $this->education['junior_high_school'];
    }
    
    public function getHighSchool()
    {
        return $this->education['high_school'];
    }
    
    public function getUniversity()
    {
        return $this->education['university'];
    }
}

Đối với cách này, ưu điểm của nó là đã phân chia các yêu cầu nhỏ thành từng class riêng, có tính tách rời, dễ dàng xử lý khi có vấn đề.

Nhưng với cách 2, ta lại gặp phải một nhược điểm đó chính là về cách thức lấy thông tin user.

<?php

require_once 'autoload.php';

$nameUser = new NameUser;
$addressUser = new AddressUser;
$educationUser = new Education;

$nameUser->getUsername();
$addressUser->getCity();
$educationUser->getHighSchool();

//

Ở đây chỉ có 3 class nên có thể bạn nghĩ sẽ nhớ được, không trở ngại. Nhưng bạn thử tưởng tượng nếu ứng dụng bạn "phình to" ra, class xử lý khởi tạo thêm rất nhiều, việc nhớ tên từng class với nhiệm vụ của nó thật sự là một vấn đề đáng quan tâm.

Nhận xét cách 2:

  • Ưu điểm: phân chia công việc cho từng class, dễ dàng bảo trì, nâng cấp

  • Nhược điểm: khó khăn trong việc nhớ cú pháp, tên và nhiệm vụ của từng class

⇒ Chưa thuyết phục.

Nhưng với facade design pattern, nó sẽ lấy ưu điểm của cả hai cách trên để tạo ra hiệu năng tuyệt vời.

Ưu điểm facade design pattern:

  1. Phân chia công việc cho từng class, dễ dàng bảo trì, nâng cấp

  2. Cung cấp cú pháp dễ nhớ, nhanh chóng

Để đáp ứng yêu cầu trên với facade design patter, chúng ta có cấu trúc thư mục như sau:

myproject/
|   autoload.php
|   data.php
|   User.php
├── Facades/
|   |   NameUser.php
|   |   AddressUser.php
|   |   Education.php
|   index.php

Ba file trong thư mục Facades thì mình sẽ giữ nguyên nội dung như ở cách 2, chỉ thêm namespace ở đầu file:

namespace Facades;

Bây giờ mình chỉ viết xử lý ở file User.php.

<?php

use Facades\NameUser;
use Facades\AddressUser;
use Facades\EducationUser;

class User
{
    protected $name, $address, $education

    public function __construct()
    {
        $this->name = new NameUser;
        $this->address = new AddressUser;
        $this->education = new EducationUser;
    }
    
    public function __call($method, $parameters)
    {
        if (method_exists($this->name, $method) == true) {
            return $this->name->{$method}();
        }

        if (method_exists($this->address, $method) == true) {
            return $this->name->{$method}();
        }

        if (method_exists($this->education, $method) == true) {
            return $this->name->{$method}();
        }
    }
}

Nếu đã học PHP OOP, chắc bạn cũng đã quá quen thuộc với magic-method __call rồi. Method __call sẽ được thực thi khi class object chứa nó gọi một method nào đó không tồn tại. Sau đó mình sẽ kiểm tra xem method đó thuộc object nào trong 3 class object đã set ở __construct rồi thực thi nó. Như vậy bạn thấy, bây giờ User đóng vai trò là một facade, vừa đạt được hiệu năng, vừa tối ưu được cú pháp.

<?php

require_once 'autoload.php';

$user = new User;

$user->getFullname();
$user->getCity();

//

Dưới đây là mô hình hoạt động của facade design pattern:

Last updated