Hướng dẫn decorator pattern trong php

Decorator Pattern là một trong các Design Pattern được giới thiệu bởi Gang of Four, đây là một pattern có tuần suất sử dụng trung bình (3/5 với thang điểm tần suất sử dụng 5). Decorator thuộc về nhóm Structural Design Pattern là các mẫu lập trình liên quan đến cấu trúc, kết cấu các đối tượng. Decorator pattern được sử dụng khi:

The new features are added by creating new classes that belong to the same type as the existing classes. Tính năng mới được thêm vào bằng cách tạo class mới có cùng dạng với class đang có.

Decorator Pattern UML

Hướng dẫn decorator pattern trong php

Mẫu Decorator bao gồm một abstract class được implement từ interface khung của các class cơ bản, từ đó nó sẽ mở rộng ra các class có thêm các tính năng. ## Ví dụ thực tế áp dụng Decorator Pattern

Chúng ta cùng đến với ví dụ về những chiếc xế hộp, một interface tạo ra khung cho các class:

interface Car {
  public function cost();
  function description();
}

Nhà máy sản xuất ra rất nhiều loại xe khác nhau từ interface Car như SUV, sedan, luxury... Ví dụ class SUV được implement từ interface Car:

class Suv implements Car {
    function cost() {
      return 30000;
    }

    function description () {
      return "Xe SUV";
    }
}

Vấn đề là khách hàng muốn thêm các tùy chọn vào xế hộp như cửa sổ trời, lốp công nghệ cao, cánh gió, ghế bọc da, gắn định vị GPS... Chúng ta không muốn thay đổi class hiện tại để thêm các tùy chọn của khách hàng vào, giải pháp nào cho vấn đề này? Decorator pattern giải quyết vấn đề này, áp dụng mẫu này cho phép bạn thêm tính năng vào các class cơ bản được implement từ một interface. Một abstract class được sử dụng, nó cũng implement từ interface trên và nó mở rộng tính năng hơn class cơ bản. Ok, trong trường hợp xế hộp ở trên chúng ta tạo ra một abstract class CarFeature implement interface Car:

abstract class CarFeature implements Car {
  protected $car;
  function __construct(Car $car) {
    $this->car = $car;
  }

  abstract function cost();
  abstract function description();
}

abstract class CarFeature có hai phương thức abstract cần được định nghĩa trong các lớp con mở rộng từ lớp này. Ví dụ, xe cần có cửa trời sẽ là một class được mở rộng từ CarFeature:

class SunRoof extends CarFeature {
    function cost () {
        return $this->car->cost() + 1500;
    }

    function description() {
        return $this->car->description() . ",  cửa sổ trời";
    }
}

Tương tự với xế hộp cần thêm tùy chọn ghế bọc da:

class LeatherSeats extends CarFeature {
    function cost () {
        return $this->car->cost() + 1000;
    }

    function description() {
        return $this->car->description() . ",  ghế bọc da";
    }
}

Tùy chọn bộ định vị toàn cầu GPS:

class GPSNavigation extends CarFeature {
    function cost () {
        return $this->car->cost() + 800;
    }

    function description() {
        return $this->car->description() . ",  định vị toàn cầu GPS";
    }
}

Chúng ta thử tạo ra các loại xe khác nhau xem tính năng của chúng như thế nào:

// Tạo ra một xe cơ bản chưa có tùy chọn
$basicCar = new Suv();

// Truyền đối tượng này vào class mới thêm tùy chọn
$carWithSunRoof = new SunRoof($basicCar);

// Kiểm tra các tính năng trên xe đã có tùy chọn
echo $carWithSunRoof -> description();
echo " giá " . $carWithSunRoof -> cost();

Kết quả như sau: Xe SUV, cửa sổ trời giá 31500 Nếu chúng ta muốn chiếc xe cơ bản có thêm nhiều các tùy chọn hơn như cả cửa sổ trời và lốp công nghệ cao, có thể truyền các class vào class cần mở rộng nhiều lần:

// 1. Tạo một xe cơ bản
$basicCar = new Suv();

// 2. Thêm tùy chọn cửa sổ trời
$carWithSunRoof = new SunRoof($basicCar);

// 3. Thêm tùy chọn ghế bọc da
$carWithSunRoofAndLeatherSeats = new LeatherSeats($carWithSunRoof);

// 4. Thêm tùy chọn GPS
$carFullOption = new GPSNavigation($carWithSunRoofAndLeatherSeats);

// 5. Kiểm tra xe với tùy chọn đầy đủ
echo $carFullOption-> description();
echo " giá " . $carFullOption-> cost();

Kết quả như sau: Xe Suv, cửa sổ trời, ghế bọc da, định vị toàn cầu GPS giá 33300

Lời kết

Decorator Pattern là pattern khá hay trong việc mở rộng tính năng một class mà không thay đổi gì các class đang có. Áp dụng Decorator Pattern trong lập trình hướng đối tượng làm cho code trở nên uyển chuyển. Danh sách các Design pattern là rất nhiều, chúng ta sẽ còn gặp lại trong các bài viết về các mẫu lập trình khác.


CÁC BÀI VIẾT KHÁC

Qua các series tự học về Design Pattern, Hôm nay cafedevn chia sẻ cho ace ví dụ và code cụ thể về cách sử dụng Decorator design pattern với ngôn ngữ lập trình PHP. Nhằm giúp ace hiểu rõ cách sử Pattern này với PHP một cách nhanh nhất và áp dụng nó vào thực tế.

Trong mẫu Decorator, một lớp sẽ thêm chức năng vào lớp khác mà không thay đổi cấu trúc của các lớp khác.

Trong ví dụ này, lớp Sách sẽ có tiêu đề của nó được BookTitleDecorator hiển thị theo các cách khác nhau và đó là các lớp con BookTitleExclaimDecorator và BookTitleStarDecorator.

Trong ví dụ của tôi, tôi làm điều này bằng cách yêu cầu BookTitleDecorator tạo một bản sao giá trị tiêu đề của Sách, giá trị này sau đó được thay đổi để hiển thị. Tùy thuộc vào việc triển khai, có thể tốt hơn nếu thực sự thay đổi đối tượng ban đầu.

Phần code

author = $author_in;
        $this->title  = $title_in;
    }
    function getAuthor() {
        return $this->author;
    }
    function getTitle() {
        return $this->title;
    }
    function getAuthorAndTitle() {
      return $this->getTitle().' by '.$this->getAuthor();
    }
}

class BookTitleDecorator {
    protected $book;
    protected $title;
    public function __construct(Book $book_in) {
        $this->book = $book_in;
        $this->resetTitle();
    }   
    //doing this so original object is not altered
    function resetTitle() {
        $this->title = $this->book->getTitle();
    }
    function showTitle() {
        return $this->title;
    }
}

class BookTitleExclaimDecorator extends BookTitleDecorator {
    private $btd;
    public function __construct(BookTitleDecorator $btd_in) {
        $this->btd = $btd_in;
    }
    function exclaimTitle() {
        $this->btd->title = "!" . $this->btd->title . "!";
    }
}

class BookTitleStarDecorator extends BookTitleDecorator {
    private $btd;
    public function __construct(BookTitleDecorator $btd_in) {
        $this->btd = $btd_in;
    }
    function starTitle() {
        $this->btd->title = Str_replace(" ","*",$this->btd->title);
    }
}

  writeln('BEGIN TESTING DECORATOR PATTERN');
  writeln('');

  $patternBook = new Book('Gamma, Helm, Johnson, and Vlissides', 'Design Patterns');
 
  $decorator = new BookTitleDecorator($patternBook);
  $starDecorator = new BookTitleStarDecorator($decorator);
  $exclaimDecorator = new BookTitleExclaimDecorator($decorator);
 
  writeln('showing title : ');
  writeln($decorator->showTitle());
  writeln('');
 
  writeln('showing title after two exclaims added : ');
  $exclaimDecorator->exclaimTitle();
  $exclaimDecorator->exclaimTitle();
  writeln($decorator->showTitle());
  writeln('');
 
  writeln('showing title after star added : ');
  $starDecorator->starTitle();
  writeln($decorator->showTitle());
  writeln('');
 
  writeln('showing title after reset: ');
  writeln($decorator->resetTitle());
  writeln($decorator->showTitle());
  writeln('');

  writeln('END TESTING DECORATOR PATTERN');

  function writeln($line_in) {
    echo $line_in."
"; } ?>

Kết quả:

BEGIN TESTING DECORATOR PATTERN

showing title : 
Design Patterns

showing title after two exclaims added : 
!!Design Patterns!!

showing title after star added : 
!!Design*Patterns!!

showing title after reset: 
Design Patterns

END TESTING DECORATOR PATTERN

Cài ứng dụng cafedev để dễ dàng cập nhật tin và học lập trình mọi lúc mọi nơi tại đây.

Tài liệu từ cafedev:

  • Full series tự học Design Pattern từ cơ bản tới nâng cao tại đây nha.
  • Các nguồn kiến thức MIỄN PHÍ VÔ GIÁ từ cafedev tại đây

Nếu bạn thấy hay và hữu ích, bạn có thể tham gia các kênh sau của cafedev để nhận được nhiều hơn nữa:

  • Group Facebook
  • Fanpage
  • Youtube
  • Instagram
  • Twitter
  • Linkedin
  • Pinterest
  • Trang chủ

Chào thân ái và quyết thắng!

Đăng ký kênh youtube để ủng hộ Cafedev nha các bạn, Thanks you!