[Phần 3] Facade pattern

Mở đầu

Trong bài viết này mình muốn giới thiệu về Facade Pattern - một pattern rất hay trong lập trình OOP, vậy điều chúng ta cần tìm hiểu đầu tiên: facade pattern là gì, ** nó có những ưu điểm gì** và vì sao nên sử dụng nó ?

Trước hết, mình xin nhắc lại ưu điểm của việc sử dụng Design Pattern là dễ hiểu, dễ bảo trì và dễ dàng mở rộng

Facade là một pattern thuộc Structural Pattern vậy nên chắc chắn nó sẽ giúp bạn thiết kế ra các đoạn code dễ hiểu hơn và có thể duy trì lâu dài, dễ dàng bảo trì hơn và vì sao lại như vậy thì mình sẽ phân tích ở ngay dưới đây.

Facade là một đối tượng và đối tượng này cung cấp một interface đơn giản để che giấu đi các xử lý phức tạp bên trong nó.

Giả sử chúng ta xây dựng một lớp thư viện vậy Facade có thể:

  • Giúp cho một thư viện của bạn trở nên đơn giản hơn trong việc sử dụng và trong việc hiểu nó, vì một mẫu Facade có các phương thức tiện lợi cho các tác vụ chung.
  • Giúp cho các đoạn mã có sử dụng thư viện trở nên dễ đọc hơn, cũng lí do như trên.
  • Giảm sự phụ thuộc của các mã code bên ngoài với hiện thực bên trong của thư viện, vì hầu hết các code đều dùng Facade, vì thế cho phép sự linh động trong phát triển các hệ thống.
  • Đóng gói tập nhiều hàm API được thiết kế không tốt bằng một hàm API đơn có thiết kế tốt hơn.

Đây chính là một ưu điểm của Facade, và dĩ nhiên cũng có câu trả lời cho vì sao lại nên sử dụng nó.

Không chỉ thế, hãy cùng phân tích một vấn đề dưới đây.

Vấn đề

Giả sử bạn có chuỗi các hành động được thực hiện theo thứ tự, và các hành động này lại được yêu cầu ở nhiều nơi trong phạm vi ứng dụng của bạn, vậy mỗi lúc bạn cần dùng đến nó bạn lại phải copy-paste hoặc viết lại đoạn code đó vào những nơi cần sử dụng trong ứng dụng. Điều này có vẻ ok, copy cũng nhanh nên chẳng sao, nhưng nếu bỗng nhiên làm xong bạn nhận ra cần phải thay đổi lại cấu trúc và mã xử lý trong hầu hết chuỗi hành động đó, vậy bạn sẽ làm gì ?

Đây chính là mấu chốt của vấn đề, bạn sẽ lại đi lục lại đoạn code đó ở tất cả các nơi, rồi lại sửa nó. Điều này quá tốn thời gian và hơn nữa dường như bạn đang mất đi sự kiểm soát các đoạn mã của mình và trong quá trình đó còn có nguy cơ phát sinh lỗi. Vậy giải pháp là gì ?

Giải pháp

Những gì bạn cần phải làm chỉ là thiết kế một Facade, và trong đó phương thức facade sẽ xử lý các đoạn code dùng đi dùng lại. Từ xu hướng quan điểm trên, chúng ta sẽ chỉ cần gọi Facade để thực thi các hành động dựa trên các parameters được cung cấp.

Bây giờ nếu chúng ta cần bất kỳ thay đổi nào trong quá trình trên, công việc sẽ đơn giản hơn rất nhiểu, chỉ cần thay đổi các xử lý trong phương thức facade của bạn và mọi thứ sẽ được đồng bộ thay vì thực hiện sự thay đổi ở những nơi sử dụng cả chuỗi các mã code đó.

Ví dụ

Hãy cùng xét một ví dụ khi người dùng mua hàng online trên trang web của bạn, vấn đề sẽ là gì ?

Một quá trình kiểm tra đơn giản bao gồm các bước sau:

  • Thêm sản phẩm vào giỏ hàng.
  • Tính toán chi phí vận chuyển.
  • Tính toán tiền chiết khấu.
  • Tạo đơn đặt hàng.

Cùng xem đoạn code mẫu


// Xử lý checkout
$productId = $_GET['productId'];
$product = Product::find($productId);
if($product->count() > 0) {

    // Thêm vào giỏ hàng
    $cart = new Cart();
    $cart->addItem($product);
    // Tính phí ship hàng
    $shipping = new ShippingCharge($product);
    $shipping->calculateCharge();

    // Tính mã giảm giá
    $discount = new Discount($product);
    $discount->applyDiscount();

    $order = new Order();
    $order->generateOrder();
    ...
}

Giả sử bạn sử dụng đoạn mã trên để xử lý đặt hàng của người dùng, có rất nhiều object sử dụng để hoàn thành chuỗi xử lý order nên việc mỗi nơi trong ứng dụng cần chuỗi xử lý này lại bê đoạn code này theo là điều hoàn toàn không nên làm.

Vậy chúng ta thử áp dụng Facade vào ví dụ trên

Ở đây mình sẽ xây dựng một lớp OrderFacade

class OrderFacade {

        private product;

        public function __construct($productId) {

            $this->product = Product::find($productId);
        }

        public function generateOrder() {
            // Xử lý toàn bộ logic
            if($this->checkQuantity()) {
                $this->addToCart();
                $this->calulateShipping();
                $this->applyDiscount();
                $this->placeOrder();
            }

        }

        private function addToCart () {
            $cart = new Cart();
            $cart->addItem($this->product);
        }

        private function checkQuantity() {
            return $this->product->count() != 0 ? true : false;
        }

        private function calulateShipping() {
            $shipping = new ShippingCharge($this->product);
            $shipping->calculateCharge();
        }

        private function applyDiscount() {
            $discount = new Discount();
            $discount->applyDiscount($this->product);
        }

        private function placeOrder() {
            $order = new Order();
            $order->generateOrder();
        }
    }

Lưu ý đây chỉ là đoạn mã để dễ hình dung cách mà Facade làm việc thôi nhé 😄

Sử dụng

$productId = $_GET['productId'];
$order = new OrderFacade($productId);
$order->generateOrder();

Như vậy đoạn mã phức tạp trên đã được gói gọn trong phương thức generateOrder và giờ mỗi khi chúng ta có thay đổi không cần phải lục lại những đoạn code như trong ví dụ ban đầu mà chỉ cần thay đổi xử lý trong Facade, và cũng không cần đem cả đoạn code dài ban đầu đi sử dụng khi cần, mà chỉ cần gọi method generateOrder()

Kết

Facade pattern chỉ nên thực hiện trong tình huống mà bạn cần một interface duy nhất để hoàn thành nhiều nhiệm vụ, tưởng tượng giống như bạn có một thư ký và cô ấy sẽ lên kế hoạch công việc theo trình tự giống như cách một Facade object làm để giúp bạn hoàn thành nhiều nhiệm vụ.

Nguồn tham khảo : Wikipedia


All Rights Reserved