GoF Design Patterns – Nó là gì mà tại sao các senior bắt buộc phải biết!

GoF Design Patterns – Nó là gì mà tại sao các senior bắt buộc phải biết!

November 8, 2021 1 By Nam Vu

Khi lập trình đến một giai đoạn nhất định, thường là khi trải qua fresher hay junior, bạn sẽ phải đối mặt với các vấn đề trong quá trình thực hiện hóa các giải pháp trong phát triển phần mềm.

Ví dụ, lập trình viên A có giải pháp A nhằm xử lý vấn đề X, nhưng lập trình B cũng có giải pháp B xử lý vấn đề X đó. Vậy làm thế nào để có thể đánh giá các giải pháp của người A và người B có hiệu quả hơn so để cùng giải quyết một vấn đề.

Khi này trong giới lập trình có một thuật ngữ gọi là Design Patterns nhằm đánh giá mức độ hiệu quả và khả năng thực tế khi ứng dụng vào sản phẩm của bạn.

Vậy thực sự thì Design Patterns là gì?

Design Patterns (hay còn gọi là mẫu thiết kế) là một giải pháp chung để giải quyết các vấn đề phổ biến khi thiết kế phần mềm trong lập trình hướng đối tượng OOP. Nó không dành riêng cho một ngôn ngữ lập trình cụ thể nào và có thể được áp dụng trong hầu hết các ngôn ngữ lập trình OOP.

Với sự đúc kết và công nhận từ nhiều nhà nghiên cứu, Design Patterns là mẫu chuẩn tối ưu nhất, có thể áp dụng để giải quyết không chỉ một vấn đề mà nhiều vấn đề có tính chất tương tự nhau, lặp đi lặp lại nhiều lần trong lập trình.

Nó được áp dụng trong toàn bộ vòng đời phát triển phần mềm, các mẫu thiết kế giúp ứng dụng có tổ chức tốt, linh hoạt, dễ bảo trì và nâng cấp. 

GoF Design Patterns là gì?

GoF là viết tắt của Gang of Four

Tác giả của DesignPatternsBook được biết đến với cái tên “Nhóm bốn người”. 

Tên của cuốn sách “Design Patterns – Elements of Reusable Object-Oriented Software” (Mẫu thiết kế: Các yếu tố của phần mềm hướng đối tượng có thể tái sử dụng)

Cho đến thời điểm hiện tại có 23 mẫu thiết kế phổ biến được định nghĩa trong cuốn sách kinh điển này.

GoF phân loại các mẫu thiết kế theo hai cách: 

Nếu chia theo mục đích của mẫu thiết kế: có ba nhóm. 

Creational gồm 5 mẫu thiết kế: Abstract Factory, Builder, Factory Method, Prototype và Singleton. 
Nhóm này mô tả việc trừu tượng hóa quá trình tạo ra các thể hiện của đối tượng, thay vì khởi tạo trực tiếp từ constructor. Lưu ý, tính đa hình không làm việc khi chúng ta tạo đối tượng. 

Structural gồm 7 mẫu thiết kế: Adapter, Bridge, Composite, Decorator, Facade, Flyweight và Proxy. 
Nhóm này mô tả cách phối hợp các lớp và các đối tượng để hình thành một cấu trúc phức tạp hơn. Nhóm này được sử dụng khi thiết kế hệ thống mới hoặc khi bảo trì, mở rộng hệ thống có sẵn. 

Behavioral gồm 11 mẫu thiết kế: Chain of Responsibility, Command, Interpreter, Iterator, Mediator, Memento, Observer, State, Strategy, Template Method và Visitor. 
Nhóm này quan tâm đến việc trao đổi thông tin, phân chia trách nhiệm, tương tác giữa các đối tượng. 

Nếu chia theo phạm vi (scope) quan hệ: có hai nhóm. 

Class: Factory Method, Adapter (class), Interpreter và Template Method. 

Object: Abstract Factory, Builder, Prototype, Singleton, Adapter (object), Bridge, Composite, Decorator, Facade, Flyweight, Proxy, Chain of Responsibility, Command, Iterator, Mediator, Memento, Observer, State, Strategy và Visitor

Trong thực tế, các mẫu thiết kế thường được dùng phối hợp với nhau. GoF trình bày quan hệ giữa các mẫu thiết kế như hình sau (các mẫu thiết kế Adapter, Bridge, Proxy có quan hệ nhưng không trình bày).

Steven John Metsker lại phân loại các mẫu thiết kế thành 5 nhóm:

Interface gồm Adapter, Facade, Composite và Bridge. Giải quyết các vấn đề liên quan đến giao diện.

Responsibility gồm Singleton, Observer, Mediator, Proxy, Chain of Responsibility và Flyweight. Giải quyết các vấn đề liên quan đến phân công trách nhiệm cho đối tượng. 

Construction gồm Builder, Factory Method, Abstract Factory, Prototype và Memento. Giải quyết các vấn đề về tạo đối tượng mà không dùng constructor. 

Operation gồm Template Method, State, Strategy, Command và Interpreter. Giải quyết các vấn đề điều khiển tác vụ, xử lý khi có nhiều tác vụ tham gia. 

Extension gồm Decorator, Iterator và Visitor. Giải quyết vấn đề mở rộng chức năng.

Vậy tại sao và khi nào dùng đến Design Patterns

Một mẫu thiết kế là sự tham chiếu giữa một vấn đề thường gặp phải khi thiết kế và một giải pháp chung:  

– Đã tạo trực tiếp các đối tượng có nhiệm vụ rõ ràng: hệ thống gắn kết chặt với cài đặt quá cụ thể sẽ làm mất đi tính linh động, khó mở rộng. 
=> Giải pháp: tạo các đối tượng một cách gián tiếp bằng cách dùng Abstract Factory, Factory Method, Prototype. 

– Phụ thuộc vào các tác vụ cụ thể: hệ thống chỉ có một cách để đáp ứng yêu cầu. 
=> Giải pháp: tránh viết code cố định bằng cách dùng Chain of Responsibility, Command. 

– Phụ thuộc vào nền tảng phần cứng hoặc phần mềm: ứng dụng khó chuyển đến các nền tảng khác. 
=> Giải pháp: giới hạn sự phụ thuộc nền tảng bằng cách dùng Abstract Factory và Bridge. 

– Phụ thuộc vào giao diện người dùng hoặc code của Client: giao diện người dùng và code của Client có thể bị thay đổi nếu các đối tượng trong hệ thống thay đổi. 
=> Giải pháp: cách ly với Client, bằng cách dùng Abstract Factory, Bridge, Memento, Proxy. 

– Phụ thuộc vào thuật toán: thuật toán sử dụng thay đổi thường xuyên. Khi thuật toán thay đổi, các đối tượng phụ thuộc nó buộc phải thay đổi. 
=> Giải pháp: cô lập thuật toán, dùng Builder, Iterator, Strategy, Template Method, Visitor. 

– Ràng buộc quá chặt chẽ: các lớp liên kết với nhau quá chặt chẽ sẽ rất khó sử dụng lại, khó kiểm tra, bảo trì.
=> Giải pháp: làm suy yếu liên kết quá chặt chẽ giữa các lớp bằng cách dùng Abstract Factory, Bridge, Chain of Responsibility, Command, Facade, Mediator, Observer. 

– Mở rộng chức năng của lớp bằng thừa kế: thừa kế (inheritance) khó sử dụng, khó hiểu hơn tổng hợp (composition). 
=> Giải pháp: mở rộng chức năng tránh dùng thừa kế mà dùng tổng hợp, với Bridge, Chain of Responsibility, Composite, Decorator, Observer, Strategy. 

– Không dễ dàng tùy biến các lớp: các lớp không thể tiếp cận, không thể hiểu hoặc khó thay đổi. 
=> Giải pháp: không can thiệp vào lớp mà mở rộng bằng cách dùng Adapter, Decorator, Visitor. 

Học các mẫu thiết kế là học kinh nghiệm từ thực tiễn, nâng cao tư duy thiết kế hướng đối tượng, nắm bắt nguyên tắc cấu trúc ứng dụng, biết cách tổ chức lại (refactoring) ứng dụng. Tuy nhiên, do bạn hoàn toàn có thể xây dựng chương trình không dùng các mẫu thiết kế, bạn cần nhận thức được lợi ích khi học và sử dụng các mẫu thiết kế. 

Học các mẫu thiết kế có thể giúp một lập trình viên rất nhiều thứ như:
– Tăng tốc độ phát triển phần mềm
– Hạn chế lỗi tiềm ẩn
– Hỗ trợ tái sử dụng code
– Giúp code dễ đọc hơn
– Đánh giá được chất lượng code của bản thân
– …

Cách tiếp cận như sau: 

– Trước tiên, bạn chấp nhận giả thuyết cho rằng các mẫu thiết kế là quan trọng trong việc thiết kế hệ thống phần mềm. 

– Thứ hai, bạn phải nhận ra rằng bạn cần phải đọc về các mẫu thiết kế để biết khi nào bạn có thể sử dụng chúng. 

– Thứ ba, bạn phải hiểu các mẫu thiết kế một cách chi tiết đủ để biết loại nào trong chúng có thể giúp bạn giải quyết yêu cầu thiết kế hoặc vấn đề gặp phải khi thiết kế. 

Theo mình, để dễ tiếp cận, bạn nên tìm hiểu theo thứ tự sau: 

Singleton, Iterator, Adapter, Decorator, State, Strategy, Factory Method, Observer, Facade, Template Method, Rồi tiếp tục lựa chọn thứ tự tìm hiểu các mẫu thiết kế còn lại. 
Chú ý xem kỹ các mẫu thiết kế khó: Abstract Factory, Interpreter và Meriator.

Bạn nên học cách ghi chú trực quan của Allen Holub về mối tương quan giữa các thành phần của mẫu thiết kế vớ hệ thống lớp đang xem xét, điều này mang lại ý nghĩa thực hành tốt trong nhận dạng mẫu thiết kế.Bài viết sẽ sử dụng phần mềm StartUML để vẽ sơ đồ thiết kế, nếu bạn chưa biết đến UML hãy dành chút thời gian đọc bài viết này nhé!
UML – Bản vẽ thi công dành cho kỹ sư lập trình

Đây là bài viết mở đầu cho loạt bài về Design Patterns dựa theo cuốn sách mà mình đã đọc và tổng hợp theo ý hiểu của bản thân với những ví dụ mà code bằng C# cho từng mẫu cụ thể. 

Các bài viết tới đây mình sẽ đi lần lượt từng Pattern một nên các bạn hãy theo dõi và ủng hộ mình nhé!

#ntechdevelopers