+8

Design Pattern cùng Flutter. Tập 4: Template Method - "Quy trình tạo nên chìa khoá"

Giới thiệu

Mỗi ngày chúng ta đều phải thức dậy, mở mắt, đi đánh răng, rửa mặt, ăn sáng, đi làm, đi về nhà, ăn uống, đánh răng, đi ngủ, cứ liên tục lặp đi lặp lại, chúng ta hoạt động như một cái máy được lập trình sẵn những hành động mà chỉ cần thức dậy là đã phải làm ngay. Việc định nghĩa ra thứ cần làm trong một ngày mà lặp lui lặp lại, gọi là định nghĩa ra một bộ khung làm việc. Điều đặt biệt là, dù ta vừa nhắm mắt ngủ vừa đánh răng thì nó chỉ ảnh hưởng đến bước đánh răng mà không ảnh hưởng gì đến bộ khung làm việc cả =)))

Việc tạo ra một bộ khung làm việc cho các hành động và khi hành động có sự thay đổi hay định nghĩa lại thì cũng không làm ảnh hưởng đến bộ khung, thì ta gọi nó là một Template Method.

Vậy...

Template Method là ai, địa chỉ nhà ở đâu?

Template Method là một loại mẫu thiết kế thuộc behaviourl được ra đời nhằm giữ nguyên một bộ khung thuật toán tránh sử dụng lặp code và những thay đổi về hành vi của subclasses sẽ không ảnh hưởng đến bộ khung ban đầu.

Ví dụ khác:

Ban đầu, chúng ta xây dựng một hệ thống xử lý file PDF, các bước xử lý gồm: đọc file, xử lý file và lưu file. Sau này hệ thống phát triển hơn, chúng ta muốn xử lý thêm cả file Excel, thậm chí cả file Word thì các bước xử lý vẫn giống như file PDF. Các bước xử lý của mỗi biến thể giống nhau nên sẽ bị lặp lại, nên ta cần một thứ để thống nhất các bước lại với nhau như là tạo ra một base class hoặc common interface để tạo nên một bộ khung thuật toán. Đây cũng là lý do mà Template Method có thể hữu ích cho chúng ta trong trường hợp này.

Thế...

Mục tiêu là gì, tại sao nó lại tồn tại

Mẫu thiết kế Template Method ra đời với hai mục tiêu chính:

  1. Tạo ra một bộ khung thuật toán để những class con tuân theo, tránh bị đúp code.
  2. Cho phép các lớp con tự định nghĩa các hàm đã được quy định mà không ảnh hưởng đến lớp cha.

Template Method Class Diagram

Cách tiếp cận tổng quát của Template Method được biểu diễn bằng sơ đồ lớp bên dưới:

  • AbstractClasss: Là lớp Abstract chứa hàm templateMethod() định nghĩa ra bộ khung thuật toán. Hàm này sẽ gọi những hàm nguyên thuỷ chưa được cài đặt.
  • ConcreteClass: Thừa kế và cài đặt các hàm nguyên thuỷ có trong AbstractClass.

Ứng dụng

Mẫu thiết kế Template Method được triển khai khi phần logic thuật toán trong khung làm việc chỉ muốn thực hiện 1 lần theo trình tự và lớp dẫn xuất của các bước có sự thay đổi. Ngoài ra, mẫu thiết kế này rất hữu ích cho việc tránh trùng lặp lại mã trong phần logic.

Thực hành

Tưởng tượng, ta có nhiều page với tiêu đề giống nhau, nhưng không muốn khi nào cũng khai báo ở mỗi page, việc khai báo giống nhau tại mỗi page sẽ khiến ta bị đúp code và gây trùng lặp, có khả năng tiềm ẩn những rủi ro. Ngoài ra, mỗi page khi fetch dữ liệu từ API, trong lúc fetch ta sẽ show ra một UI Loading để đợi đến khi gọi API thành công, thì lại gặp vấn đề đúp code như trên, mỗi page đều khai báo một UI Loading. Chung quy lại, ta chỉ cần xây dựng UI ở body, vì title và UI loading sẽ dùng chung, nên ta cần mẫu thiết kế Template Method.

Để đi vào cụ thể, đầu tiên, ta sẽ tạo một abstract class TemplateMethodBaseStateBody extend từ State, ở đây sẽ xây dựng tiêu đề chung và phần show loading

abstract class TemplateMethodBaseStateBody<T extends StatefulWidget> extends State<T> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: const PrimaryAppBar(
        title: 'Template Method',
      ),
      body: Stack(
        children: [
          buildBody(context),
          Visibility(
            visible: isLoading,
            child: buidLoading(),
          ),
        ],
      ),
    );
  }
    
  Widget buildBody(BuildContext context);

  Widget buidLoading() => const Center(
        child: CircularProgressIndicator(),
      );

  bool get isLoading => false;
}

Nhìn vào code trên, ta thấy sẽ có hàm Build được override từ State, lúc này ta dựng một khung logic chung là chứa tiêu đề và bật/tắt show loading. Đồng thời, sẽ có hàm buildBody để những page khác kế thừa từ abstract class trên sẽ phải override lại.

Tiếp đến, ta khai báo 1 page để kế thừa và sử dụng những tính năng có sẵn trên.

class TemplateMethodPage extends StatefulWidget {
  const TemplateMethodPage({super.key});

  @override
  State<TemplateMethodPage> createState() => _TemplateMethodPageState();
}

class _TemplateMethodPageState extends TemplateMethodBaseStateBody<TemplateMethodPage> {
  bool isShowLoading = false;

  @override
  bool get isLoading => isShowLoading;

  @override
  Widget buildBody(BuildContext context) {
    return Align(
      alignment: Alignment.topCenter,
      child: ElevatedButton(
        onPressed: () {
          setState(() {
            isShowLoading = !isShowLoading;
          });
        },
        child: Text(!isShowLoading ? 'Show loading' : 'Hide loading'),
      ),
    );
  }
}

Đúng như ý đồ ban đầu, ta chỉ muốn những page khác xây dựng lên UI phần body mà không ảnh hưởng đến nhau, nên chỉ cần override hàm buildBody là xong.

Thành quả

Và cuối cùng, tadaaa, đây là thành quả mà ta nhận được:

Vậy mỗi khi muốn page đều có UI Loading hay chung title thì ta chỉ cần extends custom state trên, quá tiện lợi phải không nào ^^.

Tổng kết

Với sự linh hoạt kèm theo dễ dàng tái sử dụng của Template Method, ta đã tránh được sự trùng lặp code rất nhiều. Đi theo quy trình sẽ là chìa khoá để hạn chế sai sót hơn. Tiếp đến, ở phần tiếp theo, ta sẽ đi vào một mẫu thiết kế mới toanh.Tập 5: Composite - "Chúng sinh bình đẳng"


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí