Template Method Pattern được áp dụng để định nghĩa ra cấu trúc chung, bộ khung (hay còn gọi skeleton – xương sống) chung cho một xử lý nào đó ở class cha (base class) và cho phép các lớp con (subclass) định nghĩa lại một số step trong bộ khung mà không làm thay đổi cấu trúc chung của xử lý. Design Pattern này có lẽ là pattern dễ hiểu, dễ áp dụng và quen thuộc nhất trong tất cả các patterns.
Hãy cùng phân tích tình huống sau
Giả sử chúng ta cần phát triển software cho một con robot để lắp ráp xe ô tô, tạm gọi là automotive robot. Chúng ta sẽ tạo class Robot có các method tương ứng với các công việc / động tác mà robot có thể làm và cần phải làm như sau →
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
class Robot { public: Robot() {} void start() { cout << "Starting ..." << endl; } void getParts() { cout << "Getting a carburetor ..." << endl; } void assemble() { cout << "Installing the carburetor ..." << endl; } void test() { cout << "Revving the engine ..." << endl; } void stop() { cout << "Stopping ..." << endl; } void go() { start(); getParts(); assemble(); test(); stop(); } } |
Ở đây ta có các hàm start, getParts, assemble, test, và stop tương ứng với các hành động: bắt đầu, lấy linh kiện, lắp ráp, kiểm tra, kết thúc. Bên trong các hàm là code giả lập, in ra các dòng chữ mô tả hành động của robot. Và hàm go là hàm sẽ làm cho robot hoạt động bằng cách call các hàm start, getParts, assemble, test, và stop.
OK fine ! Bây giờ chúng ta nhận được yêu cầu mới là code phần mềm cho con robot khác, lần này là robot làm bánh quy: “Cookie Robot “. Chúng ta cần phải làm gì ? Lại code từ đầu đến cuối tương tự như con robot lúc nãy ư ???
KHÔNG. Chúng ta cần phải làm khác đi, và giờ là thời điểm hợp lý để nói chuyện tiếp về Template Method Pattern.
Chúng ta đã có Automotive Robot implement bởi class Robot trước đó. Và bây giờ chúng ta cần code class cho Cookie Robot. Nếu phân tích kỹ chúng ta sẽ nhận thấy rằng Cookie Robot có một số điểm chung với Automotive Robot, nó cũng start, stop giống như Automotive Robot nhưng cần làm công việc khác. Ví dụ: hàm getParts bây giờ không phải là “Getting a carburetor …” nữa mà phải là ” Getting flour and sugar”.
Với Template Method Pattern, bạn có thể viết một method định nghĩa các step của một công việc nào đó, giống như hàm go() mà chúng ta đã tạo cho class Robot lúc trước →
1 2 3 4 5 6 7 8 |
void go() { start(); getParts(); assemble(); test(); stop(); } |
Sau đó, chúng ta biến nó thành template, bằng cách cho phép các class con (subclass) định nghĩa lại (hay override) công việc cụ thể của các step trong template đó nếu cần thiết. Đối với Cookie Robot thì chúng ta sẽ cần override các hàm getParts, assemble, và test.
Lưu ý rằng, trong trường hợp mà bạn cần override tất cả các step trong template để tạo ra một xử lý khác thì trường hợp đó áp dụng Template Method Pattern không có nhiều ý nghĩa, nó không khác gì việc code lại một class khác từ đầu đến cuối. Template Method Pattern chỉ phát huy tác dụng nếu có một vài step là giống nhau với mọi biến thể, và các biến thể khác nhau của template chỉ cần override lại một vài step trong đó mà thôi.
Áp dụng Template Method Pattern
OK. Bây giờ chúng ta sẽ áp dụng Template Method Pattern vào để giải quyết tình huống đưa ra ở trên. Ta sẽ tạo ra class base là RobotTemplate có template method là go().Khi call method go(), xử lý gồm nhiều step của bạn sẽ được thực thi. Để tùy chỉnh các step ở subclass, bạn chỉ cần override (ghi đè) các step mà bạn muốn.
*** Cụ thể trong trường hợp này ta có class diagram như sauHàm getName() mình add thêm vào, nhiệm vụ của nó là in ra tên của robot (ví dụ: Automotive Robot, Cookie Robot), để tẹo nữa code test cho dễ dàng và clear.
*** Class RobotTemplate
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
class RobotTemplate { public: virtual void start() { cout << "Starting ..." << endl; } virtual void getParts() { cout << "Getting parts ..." << endl; } virtual void assemble() { cout << "Assembling ..." << endl; } virtual void test() { cout << "Testing ..." << endl; } virtual void stop() { cout << "Stopping ..." << endl; } virtual std::string getName() = 0; void go() { start(); getParts(); assemble(); test(); stop(); } }; |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
class AutomotiveRobot : public RobotTemplate { public: AutomotiveRobot() {} void getParts() { cout << "Getting a carburetor ..." << endl; } void assemble() { cout << "Installing the carburetor ..." << endl; } void test() { cout << "Revving the engine ..." << endl; } std::string getName() { return "Automotive Robot"; } }; |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
class CookieRobot : public RobotTemplate { public: CookieRobot() {} void getParts() { cout << "Getting flour and sugar ..." << endl; } void assemble() { cout << "Baking a cookie ..." << endl; } void test() { cout << "Crunching a cookie ..." << endl; } std::string getName() { return "Cookie Robot"; } }; |
1 2 3 4 5 6 7 8 9 10 11 |
int main() { AutomotiveRobot automotiveRobot; CookieRobot cookieRobot; cout << automotiveRobot.getName() << ":" << endl; automotiveRobot.go(); cout << "---------------------" << endl; cout << cookieRobot.getName() << ":" << endl; cookieRobot.go(); return 0; } |
1 2 3 4 5 6 7 8 9 10 11 12 13 |
Automotive Robot: Starting ... Getting a carburetor ... Installing the carburetor ... Revving the engine ... Stopping ... --------------------- Cookie Robot: Starting ... Getting flour and sugar ... Baking a cookie ... Crunching a cookie ... Stopping ... |
— Phạm Minh Tuấn (Shun) —