[Design Pattern] Phần 1: Strategy Pattern


[Design Pattern] Phần 1: Strategy Pattern
Đây là phần đầu tiên trong loạt 10 phần về Design Pattern. Bài viết này hy vọng các bạn sẽ có hiểu biết về một số mẫu thiết kế, cũng như ứng dụng được vào các vấn đề các bạn gặp phải.
Trước khi đi vào phần đầu tiên. Mình sẽ giải thích sơ bộ về Design Pattern.
Design Pattern là gì?
Đó là một mô hình thiết kế để giải quyết một vấn đề thực tiễn nào đó, và vấn đề này xảy ra khá thường xuyên. Design Pattern = vấn đề + cách giải quyết vấn đề
Giả dụ như bạn gặp vấn đề: Khi càng mở rộng các lớp mới, thì việc chỉnh sửa bảo trì ngày càng cực hơn. Bạn cần một giải pháp thiết kế nào đó để giảm thiểu việc chỉnh sửa này. Bạn nhận ra rằng có nhiều người cũng gặp vấn đề giống bạn, và cũng đã xử lý được khi sử dụng giải pháp chung nào đó. Và học Design Pattern chính là học những khuôn mẫu, những giải pháp chung ấy để áp dụng giải quyết vấn đề của mình một cách nhanh chóng và hiệu quả.
Sau khi giải thích xong. Chúng ta cùng bắt đầu đi tìm hiểu về mẫu thiết kế đầu tiên: Strategy Pattern
Trước tiên ta sẽ đi tìm hiểu vấn đề
Giả sử bạn là chủ của một công ty xe. Bạn nhận một hợp đồng làm các xe như xe đạp, xe máy, xe hơi. Bạn tạo bản thiết kế như sau:

Tuyệt! Bây giờ bạn cần thêm phương thức go() để thể hiện sự di chuyển của các lớp con. Bạn viết như sau

Bây giờ chúng ta hãy thử xem các lớp con thực hiện thế nào


Ok mọi thứ đều ổn.
Bây giờ bạn mở rộng công ty, và muốn đặt thêm hàng về máy bay trực thăng, máy bay chở khách, cũng như máy bay phản lực. Bạn thiết kế lại như sau

Hmm. Việc thêm vào các lớp con lạ thế này sẽ thay đổi khá nhiều phương thức go(). Ta phải chỉnh sửa phương thức go() tại Plane, Helicopter cũng như Jet.
Đơn giản mà. Chỉ cần override lại phương thức là xong.
Đầu tiên chúng ta thêm vào 3 lớp con mới

Sau đó tại từng lớp con chúng ta override lại phương thức của nó





Bơ phệt! Code chạy khá tốt.


Vấn đề không thể nhìn thấy ở mức độ đơn giản như thế này được. Ở mức độ phức tạp hơn, khi mà có tới hàng trăm hàng nghìn các lớp con, lúc đó vấn đề mới xuất hiện.

Bạn biết rằng mỗi lớp con có thể sẽ là một thuật toán go() khác (Có thể có nhiều lớp con cùng thuật toán go() chung, như ví dụ Plane và Helicopter). Nếu sự thay đổi của thuật toán go() diễn ra khá thường xuyên, ta sẽ phải kiểm soát vị trí chứa thuật và thay đổi thuật toán, với tần suất công việc liên tục. Với các thuật toán khác nhau nằm ở các lớp rải rác như thế này thì sự kiểm soát nó cực kỳ khó khăn. Giống như việc bạn trông những đứa trẻ ở những ngôi nhà khác nhau vậy. Nếu một đứa trẻ có thông báo về vấn đề gì đó, bạn phải chạy lại ngôi nhà đó và chỉnh sửa. Việc của bạn là gom những đứa trẻ này lại một chỗ để khi xảy ra vấn đề, bạn chỉ việc tới đúng chỗ đó giải quyết vấn đề là ổn.
Cũng giống như các thuật toán go(), ta phải làm sao để tách ra khỏi lớp con, và gom nó lại một chỗ.
Thêm một vấn đề nữa là sự linh động thuật toán. Bạn thấy rằng máy bay thì phải chạy trên đường trước, rồi mới bay, rồi mới chạy trên đường. Bạn muốn thuật toán go() thay đổi từ “I’m driving” sang “I’m flying” rồi sang “I’m driving”.
Với Strategy Pattern, ngoài việc kiểm soát các thuật toán tốt hơn, bạn còn có thể thay đổi thuật toán tùy thích.
Cốt lõi của Strategy Pattern là gì? Đó chính là việc thay thế mối quan hệ “is-a” bằng quan hệ “has-a”. Thay vì việc bạn kế thừa phương thức, bạn sẽ tạo một biến chứa đối tượng là một thuật toán trên. Với mỗi đối tượng là một thuật toán, bạn hoàn toàn tách biệt thuật toán ra khỏi quan hệ “is-a” để linh động sử dụng, đồng thời có thể gom các thuật toán vào một nơi để dễ dàng kiểm soát.
(Strategy = chiến lược = thuật toán = phương thức chính xử lý một vấn đề nào đó)
Vậy chúng ta sẽ dựa vào tư tưởng này thiết kế lại nào

Ta tạo một biến lưu trữ một đối tượng là cái mà chứa thuật toán của chúng ta
Tại FlyingAlgorithm ta xây dựng thuật toán go()

Về cơ bản, ta đã chuyển được mối quan hệ “is-a” sang “has-a” rồi
Tại lớp Helicopter, ta có thể sử dụng lại thuật toán này mà không cần phải viết lại

Với các lớp con còn lại Car, Bicycle, Motorcycle, Jet, ta cũng xây dựng tương tự.




3 lớp con trên sẽ xài chung một thuật toán


Jet sẽ sử dụng thuật toán khác
Ta có thể gom các đối tượng này vào 1 package để dễ kiểm soát

Ta da. Về cơ bản ta đã tách biệt được các thuật toán ra khỏi các lớp con rồi.
Tuy nhiên ta vẫn nên tạo cho thuật toán có sự thay đổi. Ví dụ như tại lớp Plane khi nãy

Ta muốn nó có thể chuyển thuật toán DrivingAlgorithm() sang  FlyingAlgorithm() rồi sang DrivingAlgorithm() như hồi nãy ta đã nói. Mình sẽ lấy ví dụ về sự thay đổi này

Lỗi là đương nhiên rồi haha, vì mình chưa thiết kế, mà chỉ mới đưa ra ý tưởng cho sự thay đổi này.
Bạn biết rằng mỗi lớp con có một thuộc tính thể hiện thuật toán nó sử dụng. Để có thể linh động thay đổi thuật toán, ta áp dụng tính đa hình trong OOP vào đây. (Nhắc nhẹ về tính đa hình: Là khả năng thay đổi nhiều cách thể hiện của một đối tượng)
Ta có thể sử dụng Abstract hoặc Interface để tạo tính đa hình. Trong trường hợp này mình sử dụng Interface vì các class chỉ thể hiện các phương thức. Cụ thể như sau.

Tại các lớp ta implements lại GoAlgorithm và thực hiện phương thức (ở đây ta đã thực hiện rồi)


Đây là lớp Plane trước khi thay đổi

Để tạo tính đa hình, ta thay đổi biến fly với kiểu khai báo là GoAlgorithm, đồng thời ta nên đổi tên fly thành algorithm để hiểu rằng đây không phải thuật toán cố định

Ta cũng đồng thời thêm hàm set để thiết lập lại thuật toán vì algorithm là biến private.
Ta quay lại hàm test nào

*Có một chút sự thay đổi là mình khai báo plane với kiểu ban đầu là Plane để dễ sử dụng biến algorithm thay vì phải ép kiểu từng cái một (Các bạn có thể tạo biến algorithm từ lớp cha, rồi chỉnh sửa hợp lý để biến plane có tính đa hình luôn nhé)
Hãy chạy thử nào

Wow, bơ phệt!! Bạn đã đi đúng hướng rồi đấy.
Các class con còn lại bạn tự thay đổi cho phù hợp nhé
Và như thế chúng ta đã hiểu hơn về mẫu chiến lược (Strategy Pattern) rồi đấy. Khi làm việc với nhau bạn chỉ cần nhắc đến mẫu này người ta sẽ hiểu ý của bạn muốn nói mà không cần phải giải thích gì nhiều!
 Cảm ơn các bạn đã đón xem! Bài viết thứ 2 về Observer Pattern sẽ ra mắt vào tuần tới!

Nội dung bài viết thuộc về Lê Công Diễn. Có sự tham khảo từ cuốn sách Design Pattens for Dummies.

Người viết: Lê Công Diễn
Mang đi nhớ ghi nguồn

Nhận xét

  1. Tuyệt vời <3 hóng các mô hình tiếp theo, thế này thì cần gì đi học nữa, ờ nhà đọc blog cho lành :3

    Trả lờiXóa
    Trả lời
    1. Cám ơn bạn đã ủng hộ! <3 Hôm nay sẽ có bài mới luôn

      Xóa
  2. Khá dễ hiểu với một đứa khó tiếp thu như em ạ !

    Trả lờiXóa

Đăng nhận xét

Bài đăng phổ biến từ blog này

Deploy project Springboot MIỄN PHÍ sử dụng Render

Ứng dụng Mã hóa bất đối xứng (Asymmetric cryptography) vào Chữ ký số (Digital Signature)

API và HTTP - Một số khái niệm cơ bản cần biết về Web (Phần 2)