ĐẠI HỌC QUỐC GIA THÀNH PHỐ HỒ CHÍ MINH TRƯỜNG ĐẠI HỌC CÔNG NGHỆ THÔNG TIN BÁO CÁO ĐỒ ÁN 1 Đề tài SỬ DỤNG PATTERNS ĐỂ ĐỊNH NGHĨA GIẢI PHÁP PHẦN MỀM (USING PATTERNS TO DEFINE A SOFTWARE SOLUTION) Lớp SE.
Giới thiệu tổng quan
Thông tin nhóm
15520605 Phan Mạnh Phát 15520605@gm.uit.edu.vn
Giới thiệu đề tài
Với các phần mềm và ứng dụng lớn hiện này, cần một thiết kế rõ ràng và hướng tới những việc củng cố, chỉnh sửa trước và sau khi hoạt động là điều rất cần thiết Trong quá trình phát triển và vận hành sẽ xảy ra nhiều vấn đề khác nhau về nhiều mặt nên việc đảm bảo ứng dụng có thể có tính chỉnh sửa và chạy tốt thì cần một số chuẩn bị tốt kể từ khâu thiết kế hệ thống cũng như có nhưng quy tắc, các bước cụ thể trong việc lập trình để hệ thống có thể là một khối thống nhất từ đó sẽ có một phần mềm tốt sau này
Xét về vấn đề: những nhà nghiên cứu và phát triển các vấn đề trong việc thực hiện các dự án để hạn chế và giải quyết các vấn đề có thể xảy ra trong lập trình đã tiến tới phát triển các mẫu thiết kế
Xét về ứng dụng: vì đây là những phương pháp trong lập trình nên có thể ứng dụng trong nhiều dự án khác nhau với các ngôn ngữ lập trình khác nhau
Hiện nay, có rất nhiều mẫu thiết kế với mỗi mẫu sẽ gắn liền giải quyết một vấn đề nhất định trong lập trình Nhưng nổi bật hơn cả và được ứng dụng nhiều ta có 23 mẫu thiết kế được sử dụng nhiều
Tìm hiểu về các mẫu, đặc biệt là 23 mẫu thiết kế (Tìm hiểu các vấn đề mà các mẫu hướng tới để giải quyết vấn đề lập trình)
Tìm hiểu 23 mẫu thiết kế với những ưu nhược điểm của từng mẫu
Tìm hiểu vấn đề và ứng dụng của từng mẫu trong việc giải quyết vấn đề
Hiểu được các vấn đề trong lập trình, có thể ứng dụng từng mẫu để thiết kế và giải quyết các vấn đề lập trình để được một ứng dụng tốt kể cả trong việc vận hành lẫn chỉnh sửa hoặc bảo trì.
Công cụ sử dụng
Trong quá trình xây dựng phần mềm, nhóm đã sử dụng phần mềm sau:
● Eclipse IDE: IDE hỗ trợ xây dựng ứng dụng phía server (Spring Boot)
● Visual Studio Code: hỗ trợ xây dựng giao diện người dùng (ReactJS)
Design Patterns
Giới thiệu chung
Design Pattern 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.
Định nghĩa
Design pattern: là các giải pháp được nghiên cứu để giải quyết các vấn đề thường gặp trong lập trình Được tái sử dụng nhiều trong việc thiết kế phần mềm nhằm giải quyết các vấn đề phần mềm
Là một kỹ thuật trong lập trình hướng đối tượng, là một kĩ thuật lập trình nên được ứng dụng trong mọi ứng dụng với mọi ngôn ngữ lập trình khác nhau như java, C#, python,…
Sử dụng các patterns vì đây là các phương pháp tối ưu được nghiên cứu
Giải quyết các vấn đề một cách tối ưu trong việc lập trình hướng đối tượng, phù hợp với các chuyên viên vững về lập trình OOP, thiết kế và hiểu rõ một ngôn ngữ lập trình cụ thể.
Ưu điểm
Tăng tốc độ phát triển phần mềm: Cung cấp cho các developers một công cụ cụ thể để giải quyết các vấn đề thông dụng trong lập trình và thiết kế phần mềm Ngay cả khi không gặp vấn đề, vẫn có thể sử dụng các patterns để lập trình, dựa vào các nguyên tắc cụ thể mà patterns cung cấp
Code tường minh, dễ dàng Team work: Do cung cấp một nguyên tắc cụ thể nên việc lập trình của các developers sẽ dựa vào các patterns với các cấu trúc cụ thể => Tối ưu thời gian
Tái sử dụng code: Giúp các developers tái sử dụng và sửa chữa code dễ dàng hơn
Hạn chế lỗi tiềm ẩn, dễ dàng nâng cấp: Việc thực hiện dự án theo một cấu trúc cụ thể sẽ tránh được các lỗi phát sinh sau này, dễ quản lý hơn, dễ chỉnh sửa nâng cấp hơn.
Phân loại design patterns
Hiện nay, có rất nhiều mẫu được nghiên cứu và phát triển Về cơ bản ta có 3 nhóm patterns chính và 23 mẫu phổ biến thường được ứng dụng
Nhóm khởi tạo (Creational pattern)
Nhóm cấu trúc (Structural pattern)
Nhóm hành vi (Behavioral pattern)
4.1 Nhóm khởi tạo (Creational pattern)
Các patterns loại này cung cấp giải pháp để tạo ra các đối tượng và che giấu được logic của việc tạo ra nó thay vì tạo ra đối tượng theo cách trực tiếp (sử dụng từ khoá new) Điều này giúp chương trình trở nên mềm dẻo hơn trong việc quyết định đối tượng nào cần được tạo ra trong những tình huống khác nhau
4.2Nhóm cấu trúc (Structural pattern)
Những patterns loại này liên quan tới class và các thành phần của đối tượng Nó dùng để thiết lập, định nghĩa quan hệ giữa các đối tượng Hệ thống càng lớn thì mẫu này càng đóng vai trò quan trọng Ta có thể dựa vào class diagram để theo dõi mẫu này
4.3Nhóm hành vi (Behavioral Pattern)
Factory Method
Giới thiệu
Factory method là một mẫu thiết kế thuộc nhóm khởi tạo
Factory Method cung cấp một interface Nhưng để cho class con kế thừa của nó có thể ghi đè để chỉ rõ đối tượng (object) nào sẽ được tạo Factory method giao việc khởi tạo một đối tượng (object) cụ thể cho lớp con (subclass)
Giả sử ta có 3 class Dog, Cat, Duck cùng implement interface Animal
Khi mà chúng ta muốn khởi tạo ra một object có type là Animal, nhưng mà ta chưa biết sẽ phải tạo ra con chó, mèo hay con vịt mà nó phụ thuộc vào một số điều kiện, hoàn cảnh cụ thể nào đó Thì thông thường ta sẽ khởi tạo như thế này
Nhưng mà nếu như chúng ta cần sử dụng những logic để tạo ra object này ở nhiều nơi khác nhau, thì những đoạn code này sẽ bị lặp đi lặp lại Đặc biệt là khi chúng ta muốn thay đổi, chỉnh sửa hay mở rộng, ta đều phải sửa tất cả những nơi có logic đó gây mất thời gian và dễ bị sót hay lỗi
Từ đó thì Factory Method ra đời, với vai trò là một cái interface với một method mà nó sử dụng để gói cái việc khởi tạo một object vào cái method đó Tức là chúng ta sẽ đưa ra những cái business logic để khởi tạo ra những object này vào một cái factory method
Khi đó nó sẽ giúp cho code của chúng ta được gọn đi rất là nhiều so với việc sử dụng logic đấy ở nhiều nơi khác nhau Đặc biệt là nó giúp code của bạn có tính đa hình, có nghĩa là tùy vào từng trường hợp cụ thể, chúng ta có thể chọn sử dụng factory method với parameter khác nhau để quyết định khởi tạo ra object nào.
Cấu trúc
Một Factory bao gồm các thành phần cơ bản sau:
● Superclass: môt supperclass trong Factory Pattern, có thể là một interface, abstract class hay một class thông thường
● Subclasses: các subclass sẽ implement các phương thức của supperclass theo nghiệp vụ, business riêng của nó
● Factory: một class chịu tránh nhiệm khởi tạo và trả về các đối tượng subclass dựa theo tham số đầu vào
Lớp này là Singleton hoặc cung cấp một public static method cho việc truy xuất và khởi tạo đối tượng Factory class sử dụng if-else hoặc switch-case để xác định class con đầu ra
● Client: nơi sẽ gọi và sử dụng Factory để tạo lập đối tượng mong muốn
● Che giấu quá trình xử lý logic của phương thức khởi tạo
● Hạn chế sự phụ thuộc giữa creator và concrete products
● Dễ dàng mở rộng, thêm những đoạn code mới vào chương trình mà không cần phá vỡ các đối tượng ban đầu
● Giúp gom các đoạn code tạo ra product vào một nơi trong chương trình, nhờ đó giúp dễ theo dõi và thao tác
● Giảm khả năng gây lỗi compile, trong trường hợp chúng ta cần tạo một đối tượng mà quên khai báo lớp, chúng ta cũng có thể xử lý lỗi trong Factory và khai báo lớp cho chúng sau
=> Vì những đặc điểm trên nên factory pattern thường được sử dụng trong các thư viện (người sử dụng đạt được mục đích tạo mới object và không cần quan tâm đến cách nó được tạo ra)
Nhược điểm
● Source code có thể trở nên phức tạp hơn mức bình thường do đòi hỏi phải sử dụng nhiều class mới có thể cài đặt được pattern này
● Việc refactoring ( tái cấu trúc ) một class bình thường có sẵn thành một class có Factory Method có thể dẫn đến nhiều lỗi trong hệ thống, phá vỡ sự tồn tại của clients
● Factory method pattern lệ thuộc vào việc sử dụng private constructor nên các class không thể mở rộng và kế thừa.
Abstract Factory
Vấn đề
Bạn có một dự án làm một trang bán đồ nội thất Ứng dụng của bạn bao gồm các lớp sau:
1 Các sản phẩm có quan hệ với nhau như: Sofa, Chair và CoffeTable
2 Các biến thể của nhóm sản phẩm đó Ví dụ như nhóm Sofa + Chair + CoffeTable có các biến thể như Modern, Victorian và ArtDeco
Bạn cần có cách để khi tạo một đồ nội thất đơn lẻ, nó phải phù hợp với các đồ vật khác trong nhóm của nó Khách hàng sẽ khó chịu khi họ nhận về những đồ vật trong nhóm có biến thể khác nhau
Bên cạnh đó, bạn không muốn thay đổi code mỗi khi thêm sản phẩm hoặc nhóm sản phẩm trong chương trình Danh mục nội thất được cập nhật rất thường xuyên, và bạn không muốn thay đổi code mỗi khi nó diễn ra
Việc đầu tiên cần làm theo Abstract Factory là khai báo inteface rõ ràng cho mỗi sản phẩm riêng biệt trong nhóm sản phẩm Và tạo tất cả biến thể của sản phẩm theo sau inteface đó
Ví dụ tất cả biến thể của ghế được triển khai trong interface Chair, tất cả sofa được triển khai trong interface Sofa ,
Bước tiếp theo là khai báo Abstract Factory - là interface chứa tất cả phương thức tạo cho tất cả sản phẩm trong nhóm sản phẩm
(vd: createChair, createSofa và createCoffeTable) Các phương thức này trả về một kiểu sản phẩm trừu tượng (abstract) được biểu diễn bởi interface mà chúng ta trích xuất trước đó: Chair, Sofa, CoffeTable,
Vậy còn các biến thể của sản phẩm?
Với từng biến thể của nhóm sản phẩm, ta tạo ra một lớp factory riêng biệt dựa trên interface Abstract Factory Factory là lớp trả về kiểu sản phẩm riêng biệt Ví dụ, ModernFurnitureFactory có thể tạo ra các đối tượng ModernChair, ModernSofa hay ModernCoffeTable
Code client làm việc với factory hay sản phẩm thông qua interface trừu tượng Vì vậy có thể thay đổi kiểu factory hay biến thể của sản phẩm cho code client nhận mà không gây ra bất kỳ lỗi gì
Giả sử client muốn một factory để tạo ghế (chair) Nó sẽ không cần quan tâm kiểu của lớp factory đó, cũng như kiểu ghế nhận về Dù là Modern hay Victorian, nó cũng sẽ xử lý theo cùng một cách là thông qua interface trừu tượng Chair Với cách tiếp cận này client chỉ cần quan tâm là ghế sẽ triển khai phương thức sitOn như thế nào Bên cạnh đó, bất kỳ biến thể nào của chair, nó cũng sẽ phù hợp với sofa và coffe-table được tạo cùng đối tượng factory
1 Abstract Product là inteface cho các sản phẩm riêng biệt nhưng có quan hệ với nhau tạo nên một nhóm sản phẩm
2 Concrete Product là các triển khai biến thể của abstract product, được gom nhóm theo biến thể Mỗi abstract product (chair/sofa) sẽ được triển khai tất cả biến thể (modern, victorian)
3 Abstract Factory là interface có tập hợp phương thức khởi tạo cho từng abstract product
4 Concrete Factory là triển khai phương thức khởi tạo của abstract factory
Mỗi concrete factory tương ứng với biến thể cụ thể của sản phẩm và chỉ tạo sản phẩm theo biến thể đó
Bạn có thể chắc chắn rằng các sản phẩm lấy từ một factory sẽ tương thích với nhau
Tránh được kết hợp quá chặt chẽ giữa code client và concrete product
Single Responsibility Principle Bạn có thể di chuyển code tạo sản phẩm vào một nơi trong chương trình, giúp hỗ trợ code dễ dàng hơn
Open/Closed Principle Bạn có thể thêm các biến thể mới vào chương trình, mà không làm ảnh hưởng đến code client hiện tại
❌ Code có thể trở nên phức tạp khi bạn thêm vào quá nhiều interface và lớp để triển khai pattern.
Builder
Là mẫu thiết kế thuộc nhóm khởi tạo
Builder Pattern là một mẫu thiết kế được dùng để cung cấp một giải pháp linh hoạt cho các vấn đề tạo đối tượng (object) khác nhau trong lập trình hướng đối tượng
Cho phép bạn xây dựng các đối tượng phức tạp bằng cách sử du ̣ng các đối tượng đơn giản và sử du ̣ng tiếp câ ̣n từng bước Builder Pattern còn cho phép bạn tạo ra các kiểu thể hiện khác nhau của một đối tượng bằng cách sử dụng cùng một constructor code
Hãy tưởng tượng một đối tượng phức tạp đòi hỏi nhiều công sức, khởi tạo từng bước của nhiều trường và các đối tượng lồng nhau Code khởi tạo như vậy thường được chôn bên trong một hàm constructor khổng lồ với rất nhiều tham số Hoặc thậm chí tệ hơn: nằm rải rác trên toàn bộ client code
Ví dụ: hãy nghĩ về cách tạo object House Để xây dựng một ngôi nhà đơn giản, bạn cần xây dựng bốn bức tường và nền nhà, lắp cửa ra vào, lắp một cặp cửa sổ và xây dựng một mái nhà Nhưng nếu bạn muốn một ngôi nhà lớn hơn, sáng sủa hơn, có sân sau và các tiện ích khác (như hệ thống sưởi, hệ thống ống nước và hệ thống dây điện)?
Giải pháp đơn giản nhất là mở rộng lớp House và tạo một tập hợp các lớp con để bao gồm tất cả các tổ hợp của các tham số Nhưng cuối cùng bạn sẽ có một số lượng đáng kể các lớp con Bất kỳ thông số mới nào, chẳng hạn như kiểu hiên nhà, sẽ yêu cầu phát triển hệ thống phân cấp này nhiều hơn nữa
Có một cách tiếp cận khác không liên quan đến việc lai tạo các lớp con Bạn có thể tạo một phương thức constructor khổng lồ ngay trong lớp House với tất cả các tham số có thể điều khiển object house Mặc dù cách tiếp cận này thực sự loại bỏ sự cần thiết của các lớp con, nhưng nó lại tạo ra một vấn đề khác
Trong hầu hết các trường hợp, hầu hết các tham số sẽ không được sử dụng, làm cho các lần gọi constructor khá rắc rối Ví dụ, chỉ một phần nhỏ các ngôi nhà có bể bơi, vì vậy các thông số liên quan đến bể bơi sẽ vô dụng với các trường hợp khác
Pattern sắp xếp việc xây dựng object thành một tập hợp các bước (buildWalls, buildDoor, v.v.) Để tạo một object, bạn thực hiện một loạt các bước này trên một builder object Phần quan trọng là bạn không cần phải gọi tất cả các bước Bạn chỉ có thể gọi những bước cần thiết để tạo ra một cấu hình cụ thể của một object
Một số bước construction có thể yêu cầu thực hiện khác nhau khi bạn cần xây dựng các thể hiện đại diện khác nhau của sản phẩm (build various representations of the product) Ví dụ, các bức tường của một cabin có thể được xây dựng bằng gỗ, nhưng các bức tường của lâu đài phải được xây dựng bằng đá
Trong trường hợp này, bạn có thể tạo một số class builder khác nhau triển khai cùng một tập hợp các bước xây dựng, nhưng theo một cách khác Sau đó, bạn có thể sử dụng các builder này trong quá trình xây dựng (tức là một tập hợp các lệnh gọi có thứ tự đến các bước xây dựng) để tạo ra các loại object khác nhau
Ví dụ, một người thợ xây dựng mọi thứ từ gỗ và kính, người thứ hai xây dựng mọi thứ bằng đá và sắt và người thứ ba sử dụng vàng và kim cương
Bằng cách gọi cùng một nhóm các bước, bạn sẽ có được một ngôi nhà bình thường từ người xây dựng đầu tiên, một lâu đài nhỏ từ người thứ hai và một cung điện từ người thứ ba
Tuy nhiên, điều này sẽ chỉ hoạt động nếu client code gọi các bước xây dựng có thể tương tác với các builder bằng giao diện chung
Bạn có thể đi xa hơn và trích xuất một loạt lệnh gọi đến các bước của builder mà bạn sử dụng để xây dựng một sản phẩm thành một lớp riêng biệt có tên là director
Lớp Director xác định thứ tự thực hiện các bước xây dựng, trong khi trình xây dựng cung cấp việc triển khai cho các bước đó
Việc có một lớp director trong chương trình của bạn là không hoàn toàn cần thiết Bạn luôn có thể gọi các bước xây dựng theo thứ tự cụ thể trực tiếp từ client code Tuy nhiên, lớp director có thể là một nơi tốt để đưa các quy trình xây dựng khác nhau để bạn có thể sử dụng lại chúng trong chương trình của mình
Ngoài ra, lớp director hoàn toàn ẩn các product construction với client code Client chỉ cần liên kết builder với director, khởi chạy construction với director và nhận kết quả từ builder
Các thành phần trong mô hình:
Builder: Giao diện Builder khai báo các bước product construction chung cho tất cả các loại builder Abstract interface để tạo nên các đối tượng sản phẩm ( object product )
Prototype
Prototype pattern là mẫu thuộc nhóm khởi tạo
Nó có nhiệm vụ khởi tạo một object bằng cách clone một object đã tồn tại thay vì khởi tạo với từ khoá new Object mới là một bản sao có thể giống 100% với object gốc, chúng ta có thể thay đổi dữ liệu của nó mà không ảnh hưởng đến object gốc
Prototype Pattern được dùng khi việc tạo một object tốn nhiều chi phí và thời gian trong khi bạn đã có một object tương tự tồn tại
Một số điểm nổi bật:
Prototype cho phép thêm bất kỳ cá thể lớp con nào của một lớp cha đã biết trong runtime
Prototype cho phép bạn tạo ra các object mới bằng cách copy các object hiện có mà không ảnh hưởng đến bên trong của chúng Object mới là một bản sao chính xác của prototype nhưng cho phép sửa đổi mà không làm thay đổi bản gốc
Prototype được sử dụng để tránh tạo ra các lớp con của một object creator trong các ứng dụng – Tương tự Factory Method
Giả sử bạn có một object và bạn muốn tạo một bản sao chính xác của nó Đầu tiên, bạn phải tạo một object mới của cùng một lớp Sau đó, bạn phải đi qua tất cả các trường của object gốc và sao chép các giá trị của chúng sang object mới
Tuy nhiên, không phải tất cả các object đều có thể được sao chép theo cách đó vì một số trường của object có thể là private và không thể truy cập từ bên ngoài đối tượng
Có một vấn đề nữa với phương pháp tiếp cận trực tiếp vừa kể trên Vì bạn phải biết class của object để tạo bản sao, code của bạn sẽ phụ thuộc vào class đó
Nếu bạn OK với việc bị phụ thuộc đó, thì lại còn một vấn đề khác nữa Đôi khi bạn chỉ biết interface và object implement, nhưng không biết lớp cụ thể
(concrete class) của nó Ví dụ, khi một tham số trong một phương thức chấp nhận bất kỳ object nào implement một interface nào đó
Design pattern Prototype ủy thác quá trình nhân bản cho các object thực tế là đối tượng của việc nhân bản Prototype khai báo một interface chung cho tất cả các object hỗ trợ nhân bản Interface này cho phép bạn sao chép một object mà không cần couple code của bạn với class của object đó Thông thường, một interface như vậy chỉ chứa một phương thức clone duy nhất
Việc triển khai phương thức clone là rất giống nhau trong tất cả các class Phương thức này tạo một object của class hiện tại và chuyển tất cả các giá trị trường của object cũ sang một object mới Bạn thậm chí có thể sao chép các trường riêng tư vì hầu hết các ngôn ngữ lập trình đều cho phép các object truy cập các trường private của các object khác thuộc cùng một class
Chúng ta có thể rút ra được cách hoạt động của Prototype như sau:
Bạn tạo một tập hợp các object, được định cấu hình theo nhiều cách khác nhau
Khi bạn cần một object giống như object bạn đã định cấu hình, bạn chỉ cần sao chép một prototype thay vì xây dựng một object mới từ đầu
Việc triển khai phương thức clone là rất giống nhau trong tất cả các class Phương thức này tạo một object của class hiện tại và chuyển tất cả các giá trị trường của object cũ sang một object mới Bạn thậm chí có thể sao chép các trường riêng tư vì hầu hết các ngôn ngữ lập trình đều cho phép các object truy cập các trường private của các object khác thuộc cùng một class
Interface Prototype: khai báo các phương pháp nhân bản Trong hầu hết các trường hợp, người ta chỉ khai báo một phương pháp clone duy nhất
Class Concrete Prototype: implement phương thức nhân bản
Ngoài việc sao chép dữ liệu của object gốc sang bản sao, phương pháp này cũng có thể xử lý một số trường hợp biên của quá trình sao chép liên quan đến việc sao chép các Object được liên kết, gỡ rối các recursive dependencies, v.v
Client: có thể tạo một bản sao của bất kỳ object nào, miễn là object đó implement Interface prototype
*Ví dụ minh hoạ cho cấu trúc:
Trong ví dụ này, Prototype cho phép chúng ta tạo các bản sao chính xác của các object Hình mà không cần couple code với các class của chúng
Tất cả các class Hình cùng implement một interface Interface này cung cấp một phương pháp nhân bản Một class con có thể gọi phương thức clone () của class cha trước khi sao chép các giá trị các trường của chính nó vào object kết quả
Reusability – Khả năng tái sử dụng: Trong trường hợp chúng ta muốn tạo một instance của một lớp có nhiều giá trị mặc định hoặc trong cùng một process phức tạp, Prototype rất hữu ích Nhờ vào đó, chúng ta có thể tập trung vào các hoạt động khác
Reduced Initialization – Giảm thiểu việc khởi tạo: Chúng ta có thể get các instance mới với chi phí rẻ hơn Client có thể get các object mới mà không cần biết đó sẽ là loại object nào
Simple Copy Process – Quá trình sao chép đơn giản: Prototype che giấu sự phức tạp của việc tạo ra các đối tượng Chúng ta chỉ cần gọi phương thức clone(), nó đơn giản và dễ đọc
Reduced Subclassing – Giảm phân lớp: Prototype giúp giảm thiểu nhu cầu tạo ra lớp con, cho phép bạn thêm hoặc loại bỏ các object tại runtime
Quá mức cần thiết cho một dự án sử dụng rất ít object và / hoặc không có trọng tâm cơ bản về việc mở rộng chuỗi prototype
Nó cũng ẩn các lớp sản phẩm cụ thể khỏi client
Mô hình này khá tốn kém Phải chính xác trong việc xác định số lần lặp lại (iterations)
Mỗi lớp con phải triển khai các phương thức clone () hoặc các phương thức sao chép thay thế
Việc xây dựng các bản sao cho các lớp hiện có có thể phức tạp Ví dụ, việc triển khai interface Cloneable có thể bắt buộc tất cả các lớp con thực hiện phương thức clone () (một số lớp có thể không cần)
Adapter
Adapter là mẫu thiết kế thuộc nhóm cấu trúc
Adapter cho phép các đối tượng không tương thích có thể giao tiếp và tương tác với nhau mà không làm ảnh hưởng tới thuộc tính và phương thức có sẵn của các lớp liên quan
Vấn đề dẫn đến việc sử dụng mẫu Adapter trên thực tế:
Giả định rằng, nhóm kỹ sư phần mềm đang xây dựng một hệ thống theo dõi và phân tích thị trường chứng khoán Hệ thống này cần sử dụng và tương tác với rất nhiều nguồn dữ liệu chứng khoán khác nhau dưới định dạng XML, từ nguồn dữ liệu này, hệ thống sẽ sử lý và hiển thị các biểu đồ, bảng thống kê,… thân thiện với người dùng
Trong quá trình phát triển, nhóm kỹ sư phần mềm nhận thấy trên thị trường có một nền tảng cung cấp và xử lý dữ liệu chứng khoán rất thông minh và nhanh hơn các nguồn cung cấp hiện có, nhóm kỹ sư muốn tích hợp hệ thống mới vào hệ thống có sẵn Tuy nhiên, vấn đề phát sinh đó là: hệ thống mới này chỉ làm việc với dữ liệu có định dạng JSON, không tương thích với hệ thống cũ vốn được thiết kế để xử lý dữ liệu định dạng XML
Cách giải quyết đầu tiên có thể nghĩ đến đó là thay đổi hệ thống có sẵn để có thể xử lý dữ liệu với định dạng mới Tuy nhiên, việc này can thiệp đến những đoạn code đang hoạt động, có thể dẫn đến làm hư hỏng code có sẵn hoặc các thành phần có liên quan, ngoài ra cách tiếp cận này thường tốn chi phí lớn và rủi ro cao
Tạo một lớp chuyển đổi (Adapter), chức nǎng của lớp này là chuyển đổi giao tiếp (Interface) của đối tượng này cho đối tượng khác có thể hiểu và tương tác
Trong ví dụ về hệ thống chứng khoán, nhóm kỹ sư có thể tiến hành xây dựng lớp JSON-to- XML, lớp này bao bọc (Wrap) các lớp của hệ thống mới ghi đè các phương thức của hệ thống hiện tại và dịch dữ liệu dạng
JSON sang XML để hệ thống có thể xử lý mà không cần tác động đến những lớp sẵn có
Cấu trúc theo quan hệ hợp thành (composition):
Cấu trúc này sử dụng nguyên tắc hợp thành (composition principle): lớp Adapter sẽ triển khai interface của một đối tượng và bao bọc một đối tượng khác
Cấu trúc mẫu này có thể triển khai trên hầu hết các ngôn ngữ lập trình hỗ trợ hướng đối tượng và là cách triển khai phổ biến của mẫu Adapter
Cấu trúc theo quan hệ đa kế thừa:
Cấu trúc này sử dụng tính chất kế thừa của lập trình hướng đối tượng Lớp Adapter sẽ kế thừa interfaces từ ca hai lớp cần tương tác cùng lúc, sau đó ghi đè phương thức cần dùng
Cấu trúc mẫu này chỉ có thể triển khai trên các ngôn ngữ lập trình hỗ trợ đa kế thừa như C++ nên ít được sử dụng hơn
Lớp Client: Lớp chứa những nghiệp vụ có sẵn của chương trình
Client Interface: Mô ta giao thức mà các lớp khác phai tuân theo nếu muốn cộng tác với code của client
Lớp Service (Adaptee): Lớp hữu dụng (có sẵn hoặc từ bên thứ 3 Client không thể sử dụng lớp này trực tiếp vì vấn đề tương thích
Lớp Adapter: Lớp có thể hoạt động với cả Client và Service: Lớp này triển khai Client Interface, bao bọc đối tượng Service Adapter nhận yêu cầu từ Client thông qua Adapter Interface và dịch yêu cầu này cho Service nó bao bọc thành dạng mà nó có thể hiểu
Bài toán minh họa: Ứng dụng đang triển khai là ứng dụng toán học tương tác, ứng dụng có thể nhận vào tọa độ hai điểm trái dưới và phai trên của một hình chữ nhật sau đó ứng dụng sẽ vẽ và hiển thị hình chữ nhật này cho người dùng Tuy nhiên, để tiết kiệm công sức, nhóm phát triển nhận thấy ứng dụng phiên ban cũ đã có hàm vẽ và hiển thị hình chữ nhật, nhóm phát triển muốn tận dụng lại code tuy nhiên ở phiên ban cũ, hàm vẽ và hiển thị yêu cầu các tham số là tọa độ điểm trái dưới, chiều dài và chiều rộng, điều này dẫn đến sự không tương thích
Giải pháp: Thiết lập thêm một lớp mới Adapter, triển khai Interface của lớp Hình chữ nhật đồng thời chứa lớp Hình chữ nhật của phiên ban cũ Lớp Adapter sẽ nhận tọa độ điểm trái dưới và phai trên, giữ nguyên điểm trái dưới, tìm chiều dài và chiều rộng để truyền vào lớp Hình chữ nhật phiên ban cũ để thực thi
Đảm bảo tính chất đơn nhiệm - Single Responsibility principle (SRP): Có thể phân tách các interface, xử lý data, xử lý backend, ra khỏi luồng xử lý chính (primary business logic) của của chương trình
Đam bao tính chất Đóng và Mở – Open / Closed principle (OCP):
Có thể tạo một loại Adapter mới để tương tác giữa các thành phần mà vẫn đam bao toàn vẹn code có sẵn
Độ phức tạp tổng quan của code sẽ tǎng lên do phai thêm các interface và lớp Adapter Thực tế, đôi lúc sẽ đơn gian hơn nếu chỉ thay đổi các lớp service thay vì triển khai mẫu này.
Bridge
Bridge là mẫu thiết kế thuộc nhóm cấu trúc
1.1 Định nghĩa Ý tưởng của nó là tách tính trừu tượng (abstraction) ra khỏi tính hiện thực (implementation) của nó Từ đó có thể dễ dàng chỉnh sửa hoặc thay thế mà không làm ảnh hưởng đến những nơi có sử dụng lớp ban đầu Điều đó có nghĩa là, ban đầu chúng ta thiết kế một class với rất nhiều xử lý, bây giờ chúng ta không muốn để những xử lý đó trong class đó nữa Vì thế, chúng ta sẽ tạo ra một class khác và di chuyển các xử lý đó qua class mới Khi đó, trong lớp cũ sẽ giữ một đối tượng thuộc về lớp mới, và đối tượng này sẽ chịu trách nhiệm xử lý thay cho lớp ban đầu
Khi chúng ta muốn phân tách những phương thức xử lý sang một class riêng biệt để dễ dàng chỉnh sửa và thay đổi sau này, không ảnh hưởng tới các thành phần thuộc tính khác
Bridge Pattern được sử dụng được sử dụng để tách thành phần trừu tượng (abstraction) và thành phần thực thi (implementation) riêng biệt
Bridge Pattern nên được thiết kế trước khi phát triển hệ thống để
Abstraction và Implementation có thể thực hiện một cách độc lập
Một Bridge Pattern bao gồm các thành phần sau:
Client: đại diện cho khách hàng sử dụng các chức năng thông qua Abstraction
Abstraction : định ra một abstract interface quản lý việc tham chiếu đến đối tượng hiện thực cụ thể (Implementor)
Refined Abstraction (AbstractionImpl) : hiện thực (implement) các phương thức đã được định ra trong Abstraction bằng cách sử dụng một tham chiếu đến một đối tượng của Implementer
Implementor : định ra các interface cho các lớp hiện thực Thông thường nó là interface định ra các tác vụ nào đó của Abstraction
ConcreteImplementor : hiện thực Implementor interface
Một hệ thống ngân hàng cung cấp các loại tài khoản khác nhau cho khách hàng, chẳng hạn: Checking account và Saving account Chúng ta có sơ đồ như sau:
Với cách thiết kế như vậy, khi hệ thống cần cung cấp thêm một loại tài khoản khác, chúng ta phải tạo class mới cho tất cả các ngân hàng, số lượng class tăng lên rất nhiều
Bây giờ, chúng ta sẽ sử dụng Bridge Pattern để tái cấu trúc lại hệ thống trên như sau:
Với cấu trúc mới như vậy, khi có thêm một loại tài khoản mới, chúng ta đơn chỉ việc thêm vào một implement mới cho Account, các thành phần khác của Bank không bị ảnh hưởng Hoặc cần thêm một ngân hàng mới, chẳng hạn VietinBank chúng ta chỉ cần thêm implement mới cho Bank, các thành phần khác cũng không bị ảnh hưởng và số lượng class chỉ tăng lên 1
Giảm sự phục thuộc giữa abstraction và implementation
Giảm số lượng những lớp con không cần thiết
Code sẽ gọn gàn hơn và kích thước ứng dụng sẽ nhỏ hơn
Dễ dàng mở rộng về sau
Cho phép ẩn các chi tiết implement từ client
Chỉ áp dụng được cho một số ứng dụng đặt thù cần phân tách giữa abstraction và implementation
Composite
Composite là mẫu thuộc nhóm cấu trúc
Composite cho phép chúng ta sắp xếp các đối tượng thành cấu trúc cây và sau đó làm việc với các cấu trúc này như chúng là các đối tượng riêng lẻ
Tưởng tượng rằng chúng ta có 2 loại đối tượng: Sản phẩm (Product) và hộp (Box) Một chiếc hộp có thể chứa nhiều sản phẩm cũng như nhiều chiếc hộp nhỏ khác Những chiếc hộp nhỏ này cũng có thể chứa vài sản phẩm hoặc cũng có thể là chứa các hộp nhỏ hơn,…
Giả sử chúng ta quyết định tạo một hệ thống đặt hàng sử dụng những lớp này Đơn đặt hàng có thể chứa các sản phẩm đơn giản mà không có đóng gói gì, cũng như những chiếc hộp chứa các sản phẩm và cũng có thể chứa các hộp khác Làm thế nào chúng ta xác định được tổng tiền của một đơn đặt hàng như vậy
Chúng ta có thể thử cách tiếp cận trực tiếp: Mở tất cả các hộp ra, xem qua tất cả các sản phẩm và sau đó tính tổng tiền của chúng Điều đó có thể dễ dàng thực hiện trong thế giới thực Nhưng trong lập trình, điều đó không đơn giản chỉ là bỏ vào một vòng lặp Chúng ta phải biết trước được các lớp của sản phẩm và hộp mà chúng ta chuẩn bị xem xét, thêm vào đó là độ phức tạp của các chiếc hộp lồng nhau và những thứ chi tiết khó hiểu khác Tất cả những điều này làm cho cách tiếp cận trực tiếp trở nên quá khó khăn hoặc thập chí là không thể
Composite gợi ý chúng ta làm việc với Sản phẩm và Hộp thông qua một Giao diện (Interface) chung được khai báo một phương thức để tính tổng giá tiền
Cách hoạt động sẽ diễn ra như sau: Đối với một sản phẩm, nó chỉ đơn giản trả về giá tiền của nó Đối với một chiếc hộp, nó sẽ xem xét từng sản phẩm trong hộp, xem giá tiền của nó và tính tổng tiền cho cái hộp đó Nếu một trong những sản phẩm lại là một chiếc hộp khác, chiếc hộp đó cũng sẽ bắt đầu việc xem xét tất cả các sản phẩm bên trong nó và cứ tiếp tục như vậy, cho đến khi giá tiền của tất cả các thành phần (component) bên trong được tính Một chiếc hộp thậm chí có thể tính thêm phí đặc biệt, ví dụ như chi phí đóng gói
Lợi ích lớn nhất của hướng tiếp cận này là chúng ta không cần quan tâm đến lớp đối tượng cụ thể tạo nên cây Chúng ta không cần biết rằng đối tượng đó là một sản phẩm đơn giản hay là một chiếc hộp phức tạp Chúng ta có thể xử lý tất cả bọn chúng như nhau thông qua interface chung Khi chúng ta gọi một phương thức, đối tượng sẽ truyền yêu cầu xuống cây
Giao diện thành phần (Component Interface) mô tả các hoạt động chung nhất cho cả đối tượng đơn giản và phức tạp của cây
Lá (Leaf) là phần tử cơ bản của cây và không có phần tử phụ
Thông thường, các thành phần lá (Leaf) thực hiện hầu hết các công việc thực sự, vì chúng không có bất kỳ đối tượng nào để ủy quyền công việc
Container (hoặc Composite) là một phần tử có các phần tử phụ: Lá (leaves) hoặc các thùng chứa (container) khác Một thùng chứa không biết các lớp cụ thể của các lớp con của nó Nó chỉ hoạt động với tất cả các phần tử con thông qua Giao diện thành phần
Bài toán ví dụ: Mô tả chương trình quản lý hệ thống tập tin
● Trước hết chúng ta định nghĩa một Interface Component (FileComponent) có các phương thức chung cho cả folder và file Để đơn giản, chúng ta chỉ tạo 2 phương thức showProperty() và totalSize() Hai phương thức này sẽ cung cấp thông tin về file và tổng kích thước của nó
● Tiếp theo, chúng ta sẽ tạo một class Leaf cài đặt các phương thức của
Component (FileLeaf) Một class Composite chứa tập hợp các Leaf và cài đặt các phương thức của Component (FolderComposite)
● Cuối cùng, chúng ta sẽ tạo một class Client gọi các phương thức của
FileComponent và FolderComposite Cách gọi các phương thức của 2 class này hoàn toàn giống nhau do cùng implement một Component
● Chúng ta có thể làm việc với các cấu trúc cây phức tạp một cách thuận tiện hơn nhờ vào việc sử dụng tính đa hình và đệ quy
● Nguyên tắc Đóng/Mở (Open/Closed): Chúng ta có thể thêm loại đối tượng mới vào chương trình mà không phá vỡ những đoạn mã trước đó, mà những mã này thì đang làm việc với đối tượng cây
● Có thể khó tạo ra một interface chung cho các lớp với quá nhiều chức năng khác nhau Trong trường hợp nhất định, chúng ta phải tổng thể hóa lại các component interface, khiến chương trình khó hiểu và làm người khác khó tiếp thu hơn.
Decorator
Decorator là mẫu thiết kế thuộc nhóm cấu trúc
Decorator cho phép bạn đính kèm các hành vi mới vào các đối tượng bằng cách đặt các đối tượng này bên trong các đối tượng trình bao bọc đặc biệt có chứa các hành vi
Hãy tưởng tượng rằng bạn đang làm việc trên một thư viện thông báo cho phép các chương trình khác thông báo cho người dùng của họ về các sự kiện quan trọng
Phiên bản ban đầu của thư viện dựa trên lớp Notifier chỉ có một số trường, một phương thức khởi tạo và một phương thức gửi duy nhất Phương thức có thể chấp nhận một đối số thông báo từ một ứng dụng khách và gửi thông báo đến một danh sách các email đã được chuyển đến trình thông báo thông qua phương thức khởi tạo của nó Ứng dụng của bên thứ ba hoạt động như một ứng dụng khách phải tạo và định cấu hình đối tượng trình thông báo một lần, sau đó sử dụng nó mỗi khi có điều gì đó quan trọng xảy ra
Tại một số điểm, bạn nhận ra rằng người dùng thư viện mong đợi nhiều hơn là chỉ thông báo qua email Nhiều người trong số họ muốn nhận được tin nhắn SMS về các vấn đề quan trọng Những người khác muốn được thông báo trên Facebook và tất nhiên, người dùng doanh nghiệp sẽ thích nhận được thông báo từ Slack
Nó có thể khó đến mức nào? Bạn đã mở rộng lớp Trình thông báo và đưa các phương thức thông báo bổ sung vào các lớp con mới Bây giờ ứng dụng khách phải khởi tạo lớp thông báo mong muốn và sử dụng nó cho tất cả các thông báo tiếp theo
Nhưng sau đó, ai đó đã hỏi bạn một cách hợp lý rằng: “Tại sao bạn không thể sử dụng nhiều loại thông báo cùng một lúc? Nếu ngôi nhà của bạn bị cháy, bạn có thể muốn được thông báo qua mọi kênh ”
Bạn đã cố gắng giải quyết vấn đề đó bằng cách tạo các lớp con đặc biệt kết hợp nhiều phương thức thông báo trong một lớp Tuy nhiên, nhanh chóng trở nên rõ ràng rằng cách tiếp cận này sẽ làm cho mã bị phình to ra, không chỉ mã thư viện mà còn cả mã máy khách
Bạn phải tìm một số cách khác để cấu trúc các lớp thông báo sao cho số lượng của chúng không vô tình phá vỡ một số kỷ lục Guinness
Mở rộng một lớp là điều đầu tiên bạn nghĩ đến khi bạn cần thay đổi hành vi của một đối tượng Tuy nhiên, thừa kế có một số lưu ý nghiêm trọng mà bạn cần lưu ý
● Tính kế thừa là tĩnh Bạn không thể thay đổi hành vi của một đối tượng hiện có trong thời gian chạy Bạn chỉ có thể thay thế toàn bộ đối tượng bằng một đối tượng khác được tạo từ một lớp con khác
● Các lớp con có thể chỉ có một lớp cha Trong hầu hết các ngôn ngữ, kế thừa không cho phép một lớp kế thừa các hành vi của nhiều lớp cùng một lúc
Một trong những cách để khắc phục những hạn chế này là sử dụng Tổng hợp hoặc Thành phần thay vì Kế thừa Cả hai lựa chọn thay thế đều hoạt động gần như giống nhau: một đối tượng có một tham chiếu đến một đối tượng khác và ủy quyền cho nó một số hoạt động, trong khi với kế thừa, bản thân đối tượng có thể thực hiện công việc đó, kế thừa hành vi từ lớp cha của nó
Với cách tiếp cận mới này, bạn có thể dễ dàng thay thế đối tượng “helper” được liên kết bằng một đối tượng khác, thay đổi hành vi của vùng chứa trong thời gian chạy Một đối tượng có thể sử dụng hành vi của nhiều lớp khác nhau, có tham chiếu đến nhiều đối tượng và ủy quyền cho chúng tất cả các loại công việc Tổng hợp / bố cục là nguyên tắc quan trọng đằng sau nhiều mẫu thiết kế, bao gồm cả Decorator Trên lưu ý đó, chúng ta hãy quay lại cuộc thảo luận về mẫu
“Wrapper” là biệt danh thay thế cho mẫu Trang trí thể hiện rõ ràng ý tưởng chính của mẫu Trình bao bọc là một đối tượng có thể được liên kết với một số đối tượng đích Trình bao bọc chứa cùng một tập hợp các phương thức như đích và ủy quyền cho nó tất cả các yêu cầu mà nó nhận được Tuy nhiên, trình bao bọc có thể thay đổi kết quả bằng cách thực hiện điều gì đó trước hoặc sau khi nó chuyển yêu cầu đến đích
Khi nào một trình bao bọc đơn giản trở thành người trang trí thực sự? Như tôi đã đề cập, trình bao bọc thực hiện giao diện giống như đối tượng được bao bọc Đó là lý do tại sao từ quan điểm của khách hàng, các đối tượng này giống hệt nhau Làm cho trường tham chiếu của trình bao bọc chấp nhận bất kỳ đối tượng nào theo sau giao diện đó Điều này sẽ cho phép bạn bao phủ một đối tượng trong nhiều trình bao bọc, thêm hành vi kết hợp của tất cả các trình bao bọc vào nó
Faỗade
Faỗade là mẫu thiết kế thuộc nhúm cấu trỳc
Faỗade cung cấp một interface thống nhất và đơn giản cho một tập hợp cỏc interface trong một hệ thống con, do đó nó ẩn sự phức tạp của hệ thống con khỏi client Núi cỏch khỏc, Faỗade định nghĩa một interface ở cấp độ cao hơn giỳp hệ thống con dễ sử dụng hơn Faỗade liờn quan đến một lớp duy nhất cung cấp cỏc phương pháp đơn giản được yêu cầu bởi client và đại diện gọi đến các method class hệ thống hiện có
Hãy tưởng tượng rằng bạn phải làm cho code của mình hoạt động với một loạt các đối tượng thuộc một thư viện hoặc một framework có độ phức tạp cao Thông thường, bạn cần khởi tạo tất cả các đối tượng đó, theo dõi các phụ thuộc, thực thi các phương thức theo đúng thứ tự, v.v…
Do đó, business logic của các lớp của bạn sẽ bị ràng buộc, phụ thuộc chặt chẽ (tight-coupled) với các phần tử triển khai từ các lớp bên thứ ba, khiến cho việc hiểu code và duy trì trở nên khó khăn
Chỳng ta cần tạo ra một Faỗade object đại diện cho những interface phức tạp trong các hệ thống con dưới hình thức một interface đơn giản Và object này có thể thực hiện các chức năng bổ sung trước hoặc sau khi chuyển tiếp một yêu cầu Điều cho này cho phộp chỳng ta làm việc thụng qua Faỗade object đú để giảm thiểu tối đa sự phụ thuộc vào các hệ thống con
Faỗade cung cấp quyền truy cập thuận tiện vào một phần cụ thể của chức năng của hệ thống con Nó biết trực tiếp nơi yêu cầu của client và cách vận hành tất cả các bộ phận
Một lớp Additional Faỗade cú thể được tạo để ngăn chặn việc gõy ụ nhiễm cho một Faỗade đơn lẻ với cỏc tớnh năng khụng liờn quan cú thể làm cho nó trở thành một cấu trúc phức tạp khác
Hệ thống con phức hợp (Complex Subsystem) bao gồm hàng chục đối tượng khác nhau Để làm cho tất cả chúng làm điều gì đó có ý nghĩa, bạn phải đi sâu vào chi tiết triển khai của hệ thống con, chẳng hạn như khởi tạo các đối tượng theo đúng thứ tự và cung cấp cho chúng dữ liệu ở định dạng thích hợp Các lớp hệ thống con không biết về sự tồn tại của facade Chúng hoạt động trong hệ thống và làm việc trực tiếp với nhau
Client sử dụng Faỗade thay vỡ gọi trực tiếp cỏc đối tượng hệ thống con
Đơn giản hóa việc sử dụng library / collection phức tạp của các lớp
Hệ thống tích hợp thông qua Facade sẽ đơn giản hơn vì chỉ cần tương tác với Facade thay vì hàng loạt đối tượng khác
Ta có thể tách mã nguồn của mình ra khỏi sự phức tạp của hệ thống con
Làm giảm sự phụ thuộc giữa code của bạn và library / collection của các lớp
Có thể đóng gói nhiều được thiết kế không tốt bằng 1 hàm có thiết kế tốt hơn
Việc thêm một lớp gián tiếp có thể ảnh hưởng đến hiệu suất
Class Facade của bạn có thể trở lên quá lớn, làm quá nhiều nhiệm vụ với nhiều hàm chức năng trong nó Điều này sẽ dễ bị phá vỡ các quy tắc trong SOLID – nguyên lý lập trình hướng đối tượng.
Flyweight
Flyweight là mẫu thiết kế thuộc nhóm cấu trúc
Nó cho phép tái sử dụng đối tượng tương tự đã tồn tại bằng cách lưu trữ chúng hoặc tạo đối tượng mới khi không tìm thấy đối tượng phù hợp
Flyweight Pattern được sử dụng khi chúng ta cần tạo một số lượng lớn các đối tượng của 1 lớp nào đó Do mỗi đối tượng đều đòi hỏi chiếm giữ một khoảng không gian bộ nhớ, nên với một số lượng lớn đối tượng được tạo ra có thể gây nên vấn đề nghiêm trọng đặc biệt đối với các thiết bị có dung lượng nhớ thấp
Khi có một số lớn các đối tượng được ứng dụng tạo ra một cách lặp đi lặp lại
Khi việc tạo ra đối tượng đòi hỏi nhiều bộ nhớ và thời gian
Khi muốn tái sử dụng đối tượng đã tồn tại thay vì phải tốn thời gian để tạo mới
Khi nhóm đối tượng chứa nhiều đối tượng tương tự và hai đối tượng trong nhóm không khác nhau nhiều
Sử dụng Flyweight để quản lý việc tạo ra các đối tượng để tiết kiệm bộ nhớ và thời gian
Flyweight : là một interface/ abstract class, định nghĩa các các thành phần của một đối tượng
ConcreteFlyweight : triển khai các phương thức đã được định nghĩa trong Flyweight Việc triển khai này phải thực hiện các khả năng của trạng thái nội tại Đó là dữ liệu phải không thể thay đổi (unchangeable) và có thể chia sẻ (shareable) Các đối tượng là phi trạng thái (stateless) trong triển khai này Vì vậy, đối tượng ConcreteFlyweight giống nhau có thể được sử dụng trong các ngữ cảnh khác nhau
UnsharedFlyweight : mặc dù mẫu thiết kế Flyweight cho phép chia sẻ thông tin, nhưng có thể tạo ra các thể hiện không được chia sẻ (not shared) Trong những trường hợp này, thông tin của các đối tượng có thể là stateful
FlyweightFactory (Cache): lớp này có thể là một Factory Pattern được sử dụng để giữ tham chiếu đến đối tượng Flyweight đã được tạo ra Nó cung cấp một phương thức để truy cập đối tượng Flyweight được chia sẽ FlyweightFactory bao gồm một Pool (có thể là HashMap, không cho phép bên ngoài truy cập vào) để lưu trữ đối tượng Flyweight trong bộ nhớ Nó sẽ trả về đối tượng Flyweight đã tồn tại khi được yêu cầu từ Client hoặc tạo mới nếu không tồn tại
Client : sử dụng FlyweightFactory để khởi tạo đối tượng Flyweight
Ví dụ minh hoạ: Một ứng dụng game bao gồm rất nhiều Solider (lính), được chia thành các loại: Yuri, Spy,… Mỗi Solider sẽ có id và cấp độ khác nhau Thời gian để tạo một loại Solider là 3 giây
Kết quả: thay vì mất thời gian mỗi 3s cho mỗi đối tượng được khởi tạo, Flyweight sẽ tạo ra các đối tượng tương đồng với các đối tượng đã tạo trước đó và không mất them thời gian
Tiết kiệm thời gian và chi phí, đồng thời nâng cao năng suất cho ứng dụng
Cần cài đặt và thiết kế mẫu thiết kế cho phù hợp với ứng dụng.
Proxy
Proxy là mẫu thiết kế thuộc nhóm cấu trúc
Proxy Pattern là mẫu thiết kế cung cấp một đối tượng trung gian (Proxy class) có interface giống hệt một Đối tượng chính, được tất cả các truy cập trực tiếp đến Đối tượng chính đó sẽ được chuyển hướng vào Proxy Mẫu Proxy đại diện cho một đối tượng khác thực thi các phương thức, phương thức đó có thể được bổ sung, định nghĩa lại cho phù hợp với mục đích sử dụng
Trong thực tế trường hợp một server khi quá nhiều người dùng sử dụng quá nhiều tài nguyên không cần thiết ảnh hưởng đến hiệu suất của toàn hệ thống là rất thường gặp Người dùng yêu cầu quá nhiều Dữ liệu từ server một cách lần lượt nhưng không thật sự sử dụng đến chúng, để những dữ liệu sau cần thiết hơn được đi đến người dung, thì phải load toàn bộ những dữ liệu không cần thiết trước, tình trạng xảy ra nhiều có thể khiến server bị tắc nghẽn
Ta có thể cung cấp cho những đối tượng dữ liệu được người dung yêu cầu một lớp Proxy, đóng vai trò như là một người đảm nhiệm tạm thời vai trò cho những đối tượng trên Thay vì load một file ảnh có độ phân giải cao tốn nhiều công sức xử lí mà người dung có thể không cần đến, ta có thể sử dụng một đối tượng proxy ảnh có kích cỡ nhỏ hơn nhiều, khi nào server đủ tài nguyên xử lí thì mới truyền tải đối tượng thực đến cho người dùng
Nó không chỉ cung cấp cho khách hàng một giao diện duy nhất, đơn giản mà còn khiến cho người dùng không phải phụ thuộc vào quá trình xử lí của đối tượng thật Giúp cho chương trình diễn ra suôn sẻ, không phải chờ đợi những dữ liệu không cần thiết xử lí
Subject: Một interface có thể được Client truy cập đến
RealSubject: một đối tượng chứa chức năng, hành động, dữ liệu mà Client muốn truy cập
Proxy: sử dụng chung một interface Subject hệt như RealSubject để người dung có thể sử dụng giống như đang truy cập thực tiếp đến RealSubject
Client: Đối tượng thông qua Proxy để truy cập vào RealObject
Open/Closed Principle: Bạn có thể thêm proxy mới mà không cần thay đổi service hoặc clients
Có thể làm việc khi Đối tượng được gọi chưa sẵn sàng, giúp hệ thống hoạt động trơn tru, không bị ngắt quản
Quản lý lifecycle đối tượng service và điều khiển chúng mà không ảnh hưởng đến Client
Source Code có thể trở nên phức tạp hơn vì bạn cần phải thêm lớp mới
Phản hồi từ service có thể bị trì hoãn do một hành động phải truyền thông qua một lớp mới thứ 3.