Kể chuyện bé nghe - Khởi Nghiệp
Ngày 01-01-2021 (trời u ám, giông bão tố)
Phú là một developer. Một ngày nọ, khi đang ngồi ở lan can công ty, vừa nhâm nhi tách trà nóng vừa ngắm mưa rơi trắng xóa. Anh nhìn về phía xa xăm thấy tương lai mình như những tòa nhà ở đằng xa xa, tương lai đó mịt mù. Bỗng nhiên trong đầu anh lóe lên một ý tưởng, hay là mình viết một con game khuynh đảo thế giới, nổi tiếng khắp thiên hạ. Trời bỗng tạnh mưa, ánh nắng chan hòa, muôn chim đua hót, những tòa nhà ở xa xa cũng dần thấy rõ hơn.
Ngày 02-01-2021 (trời nắng nhẹ)
Ok, vạn sự khởi đầu nan. Cho nên Phú quyết định, mới viết thì viết đơn giản thui, thấy ok thì viết tiếp. Với mindset đó, logic của game lúc đầu khá đơn giản. Đây là một game lấy bối cảnh thời chiến ngày xưa, người chơi sẽ vào vai những nhân vật như vua, hoàng tử, công chúa, hoàng hậu. Vì sống trong thời chiến nên cách tốt nhất là ai cũng phải biết đánh, vì vậy, tất cả nhân vật đều có thế đánh bằng kiếm. Với logic game đơn giản và kinh nghiệm trong việc dùng OOP, anh chàng developer của chúng ta thấy mọi chuyện thật dễ dàng, tạo một class tên là Character, có phương thức tên attack và phương thức sẽ in ra đánh bằng kiếm. Sau đó, tất cả những lớp con như King, Princess, Prince, Queen chỉ cần kế thừa lớp Character là xong. Class diagram của game sẽ trong như thế này:
Sau khi code xong, bấm nút release, lau mồ hôi, cầu nguyện. Yay, cuối cùng game đã được release thành công.
Ngày 01-02-2021 (trời nắng đẹp)
May mắn thay, game càng ngày càng nổi tiếng, càng nhiều người chơi. Ngày càng nhiều yêu cầu của người chơi được gửi đến hệ thống góp ý nâng cấp một vài tính năng game.
Ngày 15-02-2021 (trời nhiều mây, tính mưa hay gì)
Phú lướt qua hàng loạt những góp ý nâng cấp game, đánh giá trải nghiệm người chơi. Bỗng nhiên anh ấy dừng lại ở một góp ý:
"Ai cũng đánh bằng kiếm thì chán quá, nếu có nhiều loại vũ kí hơn và mỗi nhân vật có vũ khí riêng cho mình thì hay biết mấy. Vua thì cầm kiếm to to, hầm hố đồ ra vẻ quyền lưc, hoàng hậu thì cho làm phù thủy đi, cầm gậy có phương hoàng ngậm ngọc cho sang chảnh. Mong là game sẽ sớm cập nhật nhiều vũ khí hơn để đa dạng hơn, cho game 5 sao."
Hmmm, hay nha, đây có thể là một bước nhảy vọt - Phú nghĩ. Ok, bắt tay vào thiết kế thôi. Sau một hồi thiết kết, Phú được class diagram như sau:
Để mỗi nhân vật có cách đánh khác nhau thì cần phải implement cách đánh đó riêng trong từng class của nhân vật đó và đều kế thừa từ abstract class Character để sure 100% là những lớp con đều có phương thức attack. Sau hàng tiếng đồng hồ code miệt mài, lần nữa, bấm nút release, lau mồ hồi, cầu nguyện. Yeahhh, update game bản 2.0 cung cấp hàng ngàn tính năng mới (thật ra là một), người chơi giờ đã có hàng ngàn vũ khí mới (chắc mỗi vũ khí là một tính năng ???), còn ngại ngùng gì mà không update ngay.
Mọi người tham khảo code bằng PHP của class diagram này tại đây nhé: https://gitlab.com/lamkimphu258/design-pattern-in-php/-/tree/develop/src/StrategyPattern/Problem
Ngày 01-03-2021 (trời nắng đẹp)
Một lần nữa, game như một quả bom, bùng nổ thị trường, khuấy đảo thế giới ảo, ngày càng nhiều người chơi hơn và song song đó cũng là nhiều góp ý hơn để cải thiện game.
Ngày 15-03-2021 (trời tối sầm, bão tố, sấm chớp)
Trong bầu trời u ám đó những tia sét lóe lên, boom, thần sấm xuất hiện. Phú giựt mình tỉnh giấc, à thì ra mơ thôi, anh lại tiếp tục đọc những góp ý. Ánh mắt lại dừng lại ở một góp ý ngắn gọn nhưng súc tích:
"Thằng làm game nghĩ gì không biết, chơi nguyên game dài vậy mà mỗi nhân vật chỉ có được một vũ khí, chán khỏi phải bàn 0 sao"
Ok, kệ, lại thêm một thằng hater - Phú thầm nghĩ. Tay anh định lăn chuột đi nhưng bỗng anh dừng lại, khoan đã, helper đội lốt hater, nếu game cho phép nhân vật có thể đổi nhiều loại vũ khí trong suốt quá trình chơi game giả sử như đánh chết quái vật thì đổi vũ khí xịn hơn hay đi dọc đường vô tình lượm được vũ khí xịn, vậy thì game sẽ hay hơn rồi. Anh lại cặm cụi thiết kế lại game, sau một hồi, anh ngã ra ghế, sắc mặt mệt mỏi, bế tắc. Thiết kế hiện tại của anh vướng phải 3 vấn đề:
- Bị mất benefit từ việc kế thừa. Vì mỗi nhân vật giờ đều tự implement phương thức attack, cho nên nếu thêm class nhân vật mới thì phải implement thêm
- Không theo nguyên tắc Open-Closed Principle. Mỗi lần muốn thay đổi cách đánh cho nhân vật, phải vào trong class và thay đổi.
- Quan trọng nhất là không thể thay đổi vũ khí ở runtime tức là khi app đang chạy, không có một cách nào để có thể thay đổi vũ khí cho nhân vật cả
Hmm, khó quá bỏ qua, đi ngủ.
Ngày 29-03-2021 (trời nắng đẹp, chim hót líu lo)
Phú vội vã chạy về nhà, bật máy tính lên, xem lại thiết kế của mình, ngồi cặm cụi vẽ vẽ, miệng thì lẩm bẩm cái gì đó. Vài tiếng trôi qua, anh đã vẽ xong một cái gì đó:
À thì ra đây là giải pháp cho những vấn đề mà game bị vướng lại không update lên được. Với bản thiết kế này, giờ đây những class con lại có benefit từ việc inheritance lớp cha, tuân thủ nguyên tắc Open-Closed Principle và hơn thế nữa là có thể thay đổi vũ khí ở runtime.
- Theo class diagram, Phú đã tách việc attack bằng nhiều cách thành những class nhỏ hơn và tất cả đều implement một cái interface để đảm bảo là phương thức attack được implement, như vậy anh đã gom những cách đánh khác nhau lại thành một nhóm.
- Sau đó anh đã dùng interface này như một property trong abstract class Character.
- Abstract class Character giờ đây có hai phương thức, attack và changeAttackStrategy. Trong phương thức attack trong abstract class Character sẽ gọi ra và dùng phương phức attack của attackStrategy. Và phương thức changeAttackStrategy là phương thức để có thể thay đổi vũ khí khi game đang chạy.
Ngon lành, update bản 3.0 với hàng triệu tính năng mới, mỗi nhân vật giờ đây có thể thay đổi và sử dụng hàng triệu vũ khí khác nhau, còn chần chờ gì mà không update.
Các bạn tham khảo code tại đây nhé: https://gitlab.com/lamkimphu258/design-pattern-in-php/-/tree/develop/src/StrategyPattern/Solution
Strategy Pattern
Giải pháp vừa rồi có tên là Strategy Pattern. Strategy Pattern là một pattern cho phép lựa chọn một strategy trong một nhóm strategy ở runtime. Đây là class diagram cho Strategy Pattern:
Nhìn hình ta thấy, những strategy na ná với nhau sẽ được gom lại thành một nhóm các strategy, tất cả đều implement một interface tên strategy. Đây là tên mẫu thôi, trong application của chúng ta, tên đó có thể là DefendStrategy, AttackStrategy, RunStrategy,... Việc sử dụng interface này sẽ giúp cho chúng ta tuân thủ nguyên tắc Coding to interfaces, not implementation.
. Với việc tuân thủ nguyên tắc này, bất kì lớp nào sử dụng những strategy này đều có sự linh hoạt, có thể thay đổi sử dụng những concreteStrategy
khác mà không cần biết chi tiết những strategy này được implement như thế nào.
Tiếp theo đó là việc thay vì sử dụng những strategy này bằng việc inheritance, chúng ta thay đổi thành composition. Việc thay đổi này sẽ giảm sự phụ thuộc, việc thay đổi context sẽ không làm thay đổi strategy và việc thay đổi strategy sẽ không làm thay đổi context. Ngoài ra, những strategy của chúng ta sẽ có thể tái sử dụng.
Cuối cùng là việc thêm thêm phương thức setStrategy để có thể thay đổi strategy ở runtime.
Làm thế nào để implement Strategy Pattern?
Nhận diện những strategy giống nhau và gom lại thành một nhóm. Sau đó inject strategy đó vào context và thêm phương thức để có thể thay đổi strategy ở runtime.
Strategy Pattern đem lại lợi ích gì
- Có benefit từ việc inheritance nếu có những concreteContext
- Tránh được violate open-closed principle
- Có thể thay đổi strategy ở runtime
Kết luận
Như vậy chúng ta đã được đọc truyện, theo dõi sự phát triển của game và vấn đề mà game gặp phải. Sau đó chúng ta biết về giải pháp để khắc phục vấn đề này. Cuối cùng là định nghĩa, giải thích, tìm hiểu về cách implement Strategy Pattern cũng như là những lợi ích mà Strategy Pattern đem lại.
Thế thì sau này, nếu các bạn có gặp một vấn đề nào đó cần thay đổi những strategy khác nhau trong một nhóm những strategy thì hãy cân nhắc Strategy Pattern nhé.