Design Patterns là các dạng thức triển khai code được các lập trình viên OOP giàu kinh nghiệm đúc kết, giúp nhanh chóng xử lý nhiều vấn đề phổ biến mà chúng ta thường gặp trong quá trình viết code. Các giải pháp này được ghi nhận qua thời gian dài và được chiêm nghiệm bởi rất nhiều nhà phát triển phần mềm.
Giới thiệu Bộ Tứ GoF (Gang of Four)
Năm 1994, có bốn tác giả lớn là Erich Gamma, Richard Helm, Ralph Johnson, và John Vlissides đã đồng xuất bản một cuốn sách có tên là Design Patterns - Elements of Reusable Object-Oriented Software (Dịch nôm na là: Các dạng thức triển khai - Những yếu tố đặc trưng của code OOP có thể tái sử dụng). Cuốn sách này đã đặt nền móng mở đầu cho thuật ngữ Design Patterns trong việc phát triển phần mềm.
Các tác giả này được biết đến với tên gọi là Bộ Tứ hay GoF (Gang of Four). Theo họ cho biết, Design Patterns chủ yếu được xây dựng dựa theo các tiêu chí sau của thiết kế hướng đối tượng:
- Lập trình hướng đến 1 interface (giao diện) chứ không phải là triển khai mở rộng interface đó.
- Ưu tiên việc tổ hợp Object hơn so với việc sử dụng hình thức kế thừa.
Ứng dụng của Design Patterns
Design Patterns có 2 ứng dụng chính trong việc phát triển phần mềm.
1. Tạo ra một nền tảng chung cho các nhà phát triển
Các Patterns cung cấp một giải pháp chung mang tính chất tiêu chuẩn mặt bằng và đặc trưng cho nhiều trường hợp. Ví dụ, Singleton được các nhà phát triển biết đến và sử dụng làm giải pháp chung để tạo ra một Object đơn nguyên duy nhất của một Class. Và họ cũng sẽ nói ra cái tên này khi được hỏi về giải pháp thực hiện một công việc như vậy.
2. Có thể được xem là các giải pháp tốt nhất
Các Patterns cũng được phát triển qua thời gian dài và mang đến những giải pháp tốt nhất cho nhiều vấn đề thường gặp khi phát triển phần mềm. Việc học các dạng thức triển khai này sẽ giúp cho các nhà phát triển phần mềm chưa có kinh nghiệm có thể học được cách thiết kế phần mềm dễ dàng hơn.
Có tất cả bao nhiêu dạng thức đã được công nhận
Nguyên gốc theo cuốn Design Patterns - Elements of Reusable Object-Oriented Software thì có tất cả 23 dạng thức triển khai và có thể được chia ra làm 3 nhóm là Khởi Tạo, Kiến Trúc, và Hành Vi - dựa trên công dụng của các dạng thức. Tuy nhiên thì ở thời điểm hiện tại con số này đã có một chút sự tăng tiến và chúng ta còn có thêm một nhóm Patterns mới là J2EE Patterns. Chúng ta sẽ cùng nhau tìm hiểu chi tiết hơn trong các bài viết tiếp theo.
- Các Patterns Khởi Tạo
Creational Patterns
- Các Patterns này cung cấp giải pháp khởi tạo các Object trong khi ẩn đi logic của tiến trình khởi tạo, thay vì khởi tạo Object trực tiếp bằng toán tử new. Cách khởi tạo này giúp đem lại sự linh động tốt hơn trong việc quyết định những Object này cần được tạo ra trong trường hợp cụ thể.
- Các Patterns Kiến Trúc
Structural Patterns
- Các Patterns này quan tâm tới vấn đề tổ hợp các Class và Object. Trong đó, tính kế thừa được áp dụng để tổ hợp các giao diện và định nghĩa cách thức tổ hợp các Object để có thêm tính năng mới.
- Các Patterns Hành Vi
Behavioral Patterns
- Các Patterns này quan tâm chủ yếu tới yếu tố giao tiếp giữa các Object.
- Các Patterns J2EE
- Các Patterns này đặc biệt quan tâm tới tầng biểu thị và được đặt nhận diện bởi Sun Java Center.
- Factory Pattern
- Abstract Factory
- Singleton Pattern
- Builder Pattern
- Prototype Pattern
- Adapter Pattern
- Bridge Pattern
- Filter Pattern
- Composite Pattern
- Decorator Pattern
- Facade Pattern
- Flyweight Pattern
Nhân tiện dịch tới đây thì mình mới nhớ ra một vấn đề quan trọng, đó là các ví dụ triển khai được sử dụng trong các bài viết tiếp theo sẽ là triển khai trên Java. Lý do mà Tutorialspoint họ chọn Java để làm Tút có lẽ cũng dễ hiểu bởi vì sự phổ biến của ngôn ngữ này và quan trọng hơn cả đó là cú pháp rườm rà, cứng nhắc, khó có thể có sự nhầm lẫn gì giữa người viết và người đọc code.
Vì vậy nên nếu như bạn chưa từng nói chuyện với máy tính bằng Java bao giờ thì rất có thể bạn sẽ cần một khóa cơ bản cấp tốc trước khi đọc bài viết tiếp theo đấy nhé.
Factory Pattern
Factory là một trong những dạng thức triển khai được sử dụng nhiều nhất và được xếp vào nhóm các dạng thức Khởi Tạo.
Trong phép triển khai Factory, chúng ta tạo ra các object
mà không để mở logic khởi tạo cho phía client (đoạn code gửi yêu cầu và sử dụng object
được khởi tạo). Thêm vào đó, việc tham chiếu tới object được khởi tạo sẽ được thực hiện thông qua một interface
(giao diện) chung thay vì sử dụng class
cụ thể.
Áp dụng triển khai
- Chúng ta sẽ tạo ra
01 interface
chung có tên làShape
cho cácclass
mô tả hình 2D (hình tròn, tam giác, hình vuông) và cácclass
cụ thể triển khaiinterface
này. - Ở bước tiếp theo,
01 class
có tên làFactory
sẽ được định nghĩa. - Cuối cùng là
main
của chương trình sẽ sử dụngFactory
để yêu cầu khởi tạo1 Shape
(hình 2D).main
sẽ truyền vào thông tin về kiểuShape
muốn khởi tạo (circle
/triangle
/square
- hình tròn / tam giác / hình vuông).
Về mặt quản lý code, chúng ta sẽ có 01 package
được đặt tên là shapefactory
.
Package
này sẽ chứa:
01 class Factory
mởpublic
01 interface Shape
mởpublic
03 class
triển khaiShape
đểdefault
Điều này có nghĩa là code client ở phía bên ngoài package
sẽ hoàn toàn không biết tới 03 class
triển khai Shape
mà chỉ có thể gọi Factory
để khởi tạo các Shape
và tham chiếu tới các object
được tạo ra thông qua interface
.
Bước 1
Tạo 01 interface
có tên là Shape
mở public
.
shapefactory/Shape.java
package shapefactory;
public interface Shape {
void draw();
}
Bước 2
Tạo các class
cụ thể triển khai interface
với access modifier
đặt default
, không mở public
.
shapefactory/Circle.java
package shapefactory;
class Circle implements Shape {
@Override
public void draw() {
System.out.println("Một hình tròn.");
}
}
shapefactory/Triangle.java
package shapefactory;
class Triangle implements Shape {
@Override
public void draw() {
System.out.println("Một hình tam giác.");
}
}
shapefactory/Square.java
package shapefactory;
class Square implements Shape {
@Override
public void draw() {
System.out.println("Một hình vuông.");
}
}
Bước 3
Tạo 01 class Factory
để sản xuất các object
thực thể với thông tin được cung cấp từ code client.
shapefactory/Factory.java
package shapefactory;
public class Factory {
public Shape createShape(String type) {
if (type == null) return null;
if (type.equalsIgnoreCase("circle")) return new Circle();
if (type.equalsIgnoreCase("triangle")) return new Triangle();
if (type.equalsIgnoreCase("square")) return new Square();
else return null;
}
}
Bước 4
Sử dụng Factory
trong code client ở main
để yêu cầu khởi tạo các object
bằng cách truyền vào thông tin về loại hình của Shape
.
PatternDemo.java
import shapefactory.Factory;
import shapefactory.Shape;
public class FactoryPatternDemo {
public static void main(String[] args) {
Factory shapeFactory = new Factory();
// Yêu cầu khởi tạo một `object` hình tròn và gọi `draw()` để vẽ
Shape circle = shapeFactory.createShape("circle");
circle.draw();
// Yêu cầu khởi tạo một `object` hình tam giác và gọi `draw()` để vẽ
Shape triangle = shapeFactory.createShape("triangle");
triangle.draw();
// Yêu cầu khởi tạo một `object` hình vuông và gọi `draw()` để vẽ
Shape square = shapeFactory.createShape("square");
square.draw();
}
}
Bước 5
Kiểm chứng lại kết quả được in ra ở console.
Một hình tròn.
Một hình tam giác.
Một hình vuông.
Abstract Factory
Abstract Factory là dạng thức được đưa ra để làm việc xoay quanh trọng tâm tạo ra một “siêu” Factory đóng vai trò tạo ra các Factory khác. Abstract Factory cũng được xếp vào nhóm các dạng thức Khởi Tạo.
Trong phép triển khai Abstract Factory, 01 interface
được sử dụng để đảm nhiệm vai trò tạo ra 01 Factory
của các object
liên quan mà không cần chỉ ra đặc định class
của các object
đó. Mỗi Factory
được khởi tạo sẽ có thể giúp chúng ta khởi tạo các object thực thể như đã biết trong bài Factory Pattern
.
Áp dụng triển khai
- Chúng ta sẽ tạo ra
01 interface Shape
mởpublic
. - Và
04 class
triển khaiinterface
này. - Ở bước tiếp theo,
01 class AbstractFactory
sẽ được tạo ra. - Kế đến là
02 class Factory
mở rộngAbstractFactory
. - Sau đó là một phương thức
static
để khởi tạo cácobject Factory
. - Cuối cùng, trong
main
ởPatternDemo
, chúng ta sử dụng phương thứcstatic
để tạo ra cácFactory
. Rồi sau đó truyền vào thông tin về kiểuobject
hình học muốn tạo ra (triangle / square).
Về mặt quản lý code, chúng ta sẽ có 01 package
được đặt tên là shapefactory
. Package
này sẽ chứa các thành phần public
tới code client bao gồm interface Shape
và class AbstractFactory
. Tất cả các thành phần còn lại bao gồm 04 class
triển khai Shape
và 02 class
mở rộng AbstractFactory
sẽ đều sử dụng access modifier
là default
.
Do đó, toàn bộ tiến trình logic để khởi tạo các Factory
cũng như các Shape
đều không để mở về phía code client và các tham chiếu đều được thực hiện thông qua abstract class
và interface
.
Bước 1
Tạo 01 interface Shape
mở public
.
shapefactory/Shape.java
package shapefactory;
public interface Shape {
void draw();
}
Bước 2
Tạo các class
triển khai giao diện Shape
.
shapefactory/NormalTriangle.java
package shapefactory;
class NormalTriangle implements Shape {
@Override
public void draw() {
System.out.println("Một hình tam giác bình thường.");
}
}
shapefactory/NormalSquare.java
package shapefactory;
class NormalSquare implements Shape {
@Override
public void draw() {
System.out.println("Một hình vuông bình thường.");
}
}
shapefactory/RoundedTriangle.java
package shapefactory;
class RoundedTriangle implements Shape {
@Override
public void draw() {
System.out.println("Một hình tam giác có các góc bo tròn.");
}
}
shapefactory/RoundedSquare.java
package shapefactory;
class RoundedSquare implements Shape {
@Override
public void draw() {
System.out.println("Một hình vuông có các góc bo tròn.");
}
}
Bước 3
Tạo 01 class abstract
để khái quát hóa các Factory
.
shapefactory/AbstractFactory.java
package shapefactory;
public abstract class AbstractFactory {
public abstract Shape createShape(String type);
}
Bước 4
Tạo các class
mở rộng AbstractFactory
giúp khởi tạo các Shape
với thông tin được cung cấp.
shapefactory/NormalFactory.java
package shapefactory;
class NormalFactory extends AbstractFactory {
@Override
public Shape createShape(String type) {
if (type == null) return null;
if (type.equalsIgnoreCase("triangle")) return new NormalTriangle();
if (type.equalsIgnoreCase("square")) return new NormalSquare();
else return null;
}
}
shapefactory/RoundedFactory.java
package shapefactory;
class RoundedFactory extends AbstractFactory {
@Override
public Shape createShape(String type) {
if (type == null) return null;
if (type.equalsIgnoreCase("triangle")) return new RoundedTriangle();
if (type.equalsIgnoreCase("square")) return new RoundedSquare();
else return null;
}
}
Bước 5
Thêm phương thức static
cho AbstractFactory
để khởi tạo các object Factory
cụ thể.
shapefactory/AbstractFactory
package shapefactory;
public abstract class AbstractFactory {
public static AbstractFactory createFactory(boolean rounded) {
if (rounded) return new RoundedFactory();
else return new NormalFactory();
}
abstract public Shape createShape(String type);
}
Bước 6
Sử dụng phương thức static
vừa rồi để khởi tạo các objectFactory
. Sau đó, sử dụng các Factory
để khởi tạo các object
hình học bằng cách truyền vào thông tin về kiểu Shape
.
PatternDemo.java
import shapefactory.AbstractFactory;
import shapefactory.Shape;
public class DesignPatterns {
public static void main(String[] args) {
// Tạo ra một Factory cho các hình 2D bình thường
AbstractFactory normalFactory = AbstractFactory.createFactory(false);
// Yêu cầu khởi tạo 1 hình tam giác bình thường và gọi `draw()`
Shape normalTriangle = normalFactory.createShape("triangle");
normalTriangle.draw();
// Yêu cầu khởi tạo 1 hình vuông bình thường và gọi `draw()`
Shape normalSquare = normalFactory.createShape("square");
normalSquare.draw();
// Tạo ra một Factory cho các hình 2D bo tròn góc
AbstractFactory roundedFactory = AbstractFactory.createFactory(true);
// Yêu cầu khởi tạo 1 hình tam giác bo góc và gọi `draw()`
Shape roundedTriangle = roundedFactory.createShape("triangle");
roundedTriangle.draw();
// Yêu cầu khởi tạo 1 hình vuông bình thường và gọi `draw()`
Shape roundedSquare = roundedFactory.createShape("square");
roundedSquare.draw();
}
}
Bước 7
Kiểm chứng lại kết quả được in ra ở console
.
Một hình tam giác bình thường.
Một hình vuông bình thường.
Một hình tam giác với các góc bo tròn.
Một hình vuông với các góc bo tròn.
Singleton Pattern
Singleton là một trong số những dạng thức triển khai đơn giản nhất của OOP và được xếp vào nhóm các dạng thức Khởi Tạo. Singleton giúp chúng ta đảm bảo chỉ có 01 object
đơn duy nhất của 01 class
đặc định được tạo ra trong suốt thời gian phần mềm hoạt động. Class
này có cung cấp một phương thức để truy xuất object
đơn nguyên này và không cho phép khởi tạo object
mới tương tự ở bất kỳ nơi nào khác.
Áp dụng triển khai
- Chúng ta sẽ tạo ra
01 class
có tên làThing
. Bên trongclass
này sẽ có hàm khởi tạo được khóaprivate
và một thuộc tínhstatic
để lưu tham chiếu củaobject
duy nhất được tạo ra. Thing
có cung cấp một phương thứcstatic
để chia sẻ tham chiếu tớiobject
duy nhất cho phần code client sử dụng.- Cuối cùng là
main
ởPatternDemo
sẽ sử dụngThing
để hỏi truy xuất tớiobject
duy nhất và hiển thị tin nhắn củaobject
đó.
Bước 1
Tạo 01 class singleton
có tên là Thing
.
singleton/Thing.java
package singleton;
public class Thing {
private static final Thing instance = new Thing();
private Thing() {
// khóa `private` phương thức khởi tạo với `new` từ bên ngoài `class`
}
public static Thing getInstance() {
return instance;
}
public void showMessage() {
System.out.println("Tin nhắn từ `object` duy nhất của `Thing`!");
}
}
Bước 2
Truy xuất tới object
duy nhất và hiển thị tin nhắn.
PatternDemo.java
import singleton.Thing;
public class PatternDemo {
public static void main(String[] args) {
// Lỗi biên dịch vì phương thức khởi tạo không khả dụng
// Thing only = new Thing();
// Truy xuất object đơn nguyên duy nhất
Thing only = Thing.getInstance();
// Hiển thị tin nhắn
only.showMessage();
}
}
Bước 3
Kiểm chứng lại kết quả được in ra ở console
.
Tin nhắn từ `object` duy nhất của `Thing`!
Builder Pattern
Builder Pattern giúp chúng ta xây dựng một object
phức tạp từ những object
đơn giản qua từng bước. Builder Pattern được xếp vào nhóm các pattern Khởi .
Một class Builder
luôn luôn xây dựng object
thực thể cuối cùng qua từng bước. Bản thân Builder
luôn luôn độc lập và không lệ thuộc vào các object
khác.
Áp dụng triển khai
- Chúng ta sẽ tạo 01 abstract class Item để biểu thị hàng hóa và các class để mô tả các thực thể.
- Giao diện Packing thể hiện cách đóng gói của sản phẩm.
- Sau đó, chúng ta tạo ra 01 class Meal có chứa một mảng Item.
- Và tạo 01 class Builder để thực hiện công việc tổ hợp nên các object Meal khác nhau bằng việc kết hợp các Item.
- Cuối cùng là code main sử dụng Builder để xây dựng một Meal.
Về mặt quản lý code, chúng ta sẽ có 01 package
có tên là mealbuilder
. Phần code client trên main
sẽ chỉ cần tham chiếu tới Builder
và Meal
do đó sẽ chỉ có 02 class
này được mở public
. Tất cả các thành phần còn lại của package
sẽ đều được đặt access modifier
là default
.
Bước 1
Tạo abstract class Item
và interface Packing
.
mealbuilder/Item.java
package mealbuilder;
abstract class Item {
private final String name;
private final float price;
private final Packing packing;
Item(
String name,
float price,
Packing packing
) {
this.name = name;
this.price = price;
this.packing = packing;
}
public String getName() {
return name;
}
public float getPrice() {
return price;
}
public Packing getPacking() {
return packing;
}
}
mealbuilder/Packing.java
package mealbuilder;
interface Packing {
public String pack();
}
Bước 2
Tạo các class
triển khai của Packing
.
mealbuilder/Wrapper.java
package mealbuilder;
class Wrapper
implements Packing {
@Override
public String pack() {
return "Wrapper";
}
}
mealbuilder/Bottle.java
package mealbuilder;
class Bottle
implements Packing {
@Override
public String pack() {
return "Bottle";
}
}
Bước 3
Tạo các class Burger
mở rộng Item
.
mealbuilder/Burger.java
package mealbuilder;
public abstract class Burger extends Item {
Burger(
String name,
float price,
Packing packing
) {
super(name, price, packing);
}
}
mealBuilder/BurgerForVeg.java
package mealbuilder;
class BurgerForVeg extends Burger {
BurgerForVeg() {
super("Bugger for Veg", 25.0f, new Wrapper());
}
}
mealBuilder/BurgerNonVeg.java
package mealbuilder;
class BurgerNonVeg extends Burger {
BurgerNonVeg() {
super("Burger non-Veg", 50.5f, new Wrapper());
}
}
Bước 4
Tạo các class Drink
mở rộng Item
tương tự như Burger
.
mealbuilder/Drink.java
package mealbuilder;
public abstract class Drink extends Item {
Drink(
String name,
float price,
Packing packing
) {
super(name, price, packing);
}
}
mealbuilder/DrinkCoke.java
package mealbuilder;
class DrinkCoke extends Drink {
DrinkCoke() {
super("Coke", 30.0f, new Bottle());
}
}
mealbuilder/DrinkPepsi.java
package mealbuilder;
class DrinkPepsiextends Drink {
DrinkPepsi() {
super("Pepsi", 35.0f, new Bottle());
}
}
Bước 5
Tạo class Meal
chứa các object Item
.
mealbuilder/Meal.java
package mealbuilder;
import java.util.ArrayList;
import java.util.List;
public class Meal {
private List<Item> itemList = new ArrayList<Item>();
public void addItem(Item newItem) {
itemList.add(newItem);
}
public float getCost() {
float totalCost = 0.0f;
for (Item i : itemList) { totalCost += i.getPrice(); }
return totalCost;
}
public void showItems() {
for (Item i : itemList) {
System.out.println(i.getName());
System.out.println(" + Packing: " + i.getPacking().pack());
System.out.println(" + Price: " + i.getPrice());
}
}
}
Bước 6
Tạo class Builder
giúp xây dựng các object Meal
.
mealbuilder/Builder.java
package mealbuilder;
public class Builder {
public Meal prepareMealForVeg() {
Meal mealForVeg = new Meal();
mealForVeg.addItem(new BurgerForVeg());
mealForVeg.addItem(new DrinkCoke());
return mealForVeg;
}
public Meal prepareMealNonVeg() {
Meal mealNonVeg = new Meal();
mealNonVeg.addItem(new BurgerNonVeg());
mealNonVeg.addItem(new DrinkPepsi());
return mealNonVeg;
}
}
Bước 7
Code main
tại PatternDemo
để chạy thử pattern.
PatternDemo.java
import mealbuilder.Builder;
import mealbuilder.Meal;
public class PatternDemo {
public static void main(String[] args) {
Builder mealBuilder = new Builder();
Meal mealForVeg = mealBuilder.prepareMealForVeg();
System.out.println("=== Meal for Veg ========");
mealForVeg.showItems();
System.out.println("Total cost: " + mealForVeg.getCost());
Meal mealNonVeg = mealBuilder.prepareMealNonVeg();
System.out.println("=== Meal non-Veg ========");
mealNonVeg.showItems();
System.out.println("Total cost: " + mealNonVeg.getCost());
}
}
Bước 8
Kiểm chứng lại thông tin được in ra ở console
.
=== Meal for Veg ========
Bugger for Veg
+ Packing: Wrapper
+ Price: 25.0
Coke
+ Packing: Bottle
+ Price: 30.0
Total cost: 55.0
=== Meal non-Veg ========
Burger non-Veg
+ Packing: Wrapper
+ Price: 50.5
Pepsi
+ Packing: Bottle
+ Price: 35.0
Total cost: 85.5
Prototype Pattern
Prototype Pattern thường được sử dụng để nhân bản một object
với lưu ý về mặt hiệu năng xử lý. Protype Pattern được xếp vào nhóm các pattern Khởi Tạo.
Prototype Pattern sử dụng một giao diện kiểu mẫu để tạo ra một bản sao clone
của một object
. Pattern này được sử dụng khi mà việc khởi tạo một object
trực tiếp gặp nhiều khó khăn và tiêu tốn tài nguyên về thời gian.
Ví dụ, một object
được tạo ra sau một lần mở kết nối tới CSDL có thể được cho là đắt đỏ. Chúng ta có thể ghi đệm lại object
này vào một cache
, và lần tới khi yêu cầu được gửi đến, chúng ta có thể tạo ra một bản sao clone
và trả về, đồng thời cập nhật CSDL, thay vì truy xuất lại thông tin từ CSDL và khởi tạo lại object
từ đầu.
Áp dụng triển khai
- Chúng ta sẽ tạo
01 abstract class Shape
. - Và các
class
mô tả thực thể mở rộngShape
. - Ở bước tiếp theo,
01 class Cache
ghi đệm cácobject
đã được tạo ra sẽ được định nghĩa. - Cuối cùng là code
main
ởPatternDemo
sẽ sử dụngCache
để yêu cầu lấy cácobject Shape
.
Về mặt quản lý code, chúng ta sẽ có 01 package
tên là shapeprototype
. Code sử dụng ở phía client sẽ chỉ có thể tham chiếu tới Cache
và Shape
được mở public
. Các thành phần còn lại của shapeprototype
đều được đặt access modifier
là default
.
Bước 1
Tạo 01 abstract class Shape
triển khai interface Cloneable
.
shapeprototype/Shape.java
package shapeprototype;
public abstract class Shape
implements Cloneable {
private String id;
protected String type;
public abstract void draw();
public String getType() {
return type;
}
public void setId(String id) {
this.id = id;
}
public String getId() {
return id;
}
@Override
public Object clone() {
Object clone = null;
try {
clone = super.clone();
}
catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return clone;
}
}
Bước 2
Tạo các class
mô tả thực thể mở rộng Shape
.
shapeprototype/Circle.java
package shapeprototype;
class Circle extends Shape {
public Circle() {
type = "circle";
}
@Override
public void draw() {
System.out.println("Một hình tròn.");
}
}
shapeprototype/Triangle.java
package shapeprototype;
class Triangle extends Shape {
public Triangle() {
type = "triangle";
}
@Override
public void draw() {
System.out.println("Một hình tam giác.");
}
}
shapeprototype/Square.java
package shapeprototype;
class Square
extends Shape {
public Square() {
type = "square";
}
@Override
public void draw() {
System.out.println("Một hình vuông.");
}
}
Bước 3
Tạo 01 class Cache
để truy vấn các object Shape
từ CSDL và ghi đệm vào một.
shapeprototype/Cache.java
package shapeprototype;
import java.util.Hashtable;
public class Cache {
private static Hashtable<String, Shape> cachedShapes = new Hashtable<String, Shape>();
public static Shape getShape(String id) {
return (Shape) cachedShapes.get(id).clone();
}
public static void load() {
Circle circle = new Circle();
circle.setId("1");
cachedShapes.put(circle.getId(), circle);
Triangle triangle = new Triangle();
triangle.setId("2");
cachedShapes.put(triangle.getId(), triangle);
Square square = new Square();
square.setId("3");
cachedShapes.put(square.getId(), square);
}
}
Bước 4
Tại PatternDemo
, sử dụng Cache
để tạo ra các bản sao của các object Shape
được ghi đệm trước đó trong Hashtable
.
PatternDemo.java
import shapeprototype.Cache;
import shapeprototype.Shape;
public class PatternDemo {
public static void main(String[] args) {
Cache.load();
Shape clonedCircle = (Shape) Cache.getShape("1");
System.out.println("=== Cloned " + clonedCircle.getType());
clonedCircle.draw();
Shape clonedTriangle = (Shape) Cache.getShape("2");
System.out.println("=== Cloned " + clonedTriangle.getType());
clonedTriangle.draw();
Shape clonedSquare = (Shape) Cache.getShape("3");
System.out.println("=== Cloned " + clonedSquare.getType());
clonedSquare.draw();
}
}
Bước 5
Kiểm chứng lại thông tin được in ra ở console
.
=== Cloned circle
Một hình tròn.
=== Cloned triangle
Một hình tam giác.
=== Cloned square
Một hình vuông.
Adapter Pattern
Adapter Pattern hoạt động như một cầu nối giữa 2 giao diện không tương thích. Adapter Pattern được xếp vào nhóm các pattern Kiến Trúc.
Adapter Pattern sử dụng 01 class
đơn để đảm nhiệm việc kết nối các giao diện độc lập hoặc không tương thích.
Áp dụng triển khai
- Chúng ta có 01
interface MediaPlaying
và01 class
mô tả thực thểMediaPlayer
triển khai giao diện đó. Mặc định thìMediaPlayer
có thể phát các tệp định dạng Mp3. - Ngoài ra chúng ta còn có
01 interface AdvancedPlaying
và cácclass
triển khai giao diện bổ sung này. Cácclass
này có thể phát các tệp Vlc hoặc Mp4. - Bây giờ thì chúng ta muốn
MediaPlayer
có thể phát được các tệp định dạng khác nữa ngoài Mp3 mặc định. - Để làm được điều này, chúng ta cần tạo
01 class MediaAdapter
triển khaiinterface MediaPlaying
và sử dụng cácobject AdvancedPlaying
để phát tệp được yêu cầu. MediaPlayer
sẽ sử dụngMediaApdater
bằng cách truyền vào đó định dạng media muốn phát mà không cần biết tới các class thực thụ được sử dụng để phát tệp.- Cuối cùng là PatternDemo với code main sẽ sử dụng MediaPlayer để phát các định dạng media khác nhau.
Về mặt quản lý code, chúng ta có 01 package adapterpattern
. Code client trong main
sẽ chỉ tham chiếu duy nhất tới MediaPlayer
và trỏ interface MediaPlaying
. Do đó chúng ta sẽ chỉ có duy nhất class MediaPlayer
và interface MediaPlaying
để mở public
. Tất cả các thành phần còn lại của package
đều sẽ được đặt access modifier
là default
.
Bước 1
Tạo các interface
để phát tệp bao gồm MediaPlaying
và AdvancedPlaying
.
adapterpattern/MediaPlaying.java
package adapterpattern;
public interface MediaPlaying {
public MediaPlaying play(String mediaType, String fileName);
}
adapterpattern/AdvancedPlaying.java
package adapterpattern;
interface AdvancedPlaying {
public AdvancedPlaying playVlc(String fileName);
public AdvancedPlaying playMp4(String fileName);
}
Bước 2
Tạo các class triển khai interface AdvancedPlaying
.
adapterpattern/VlcPlayer.java
package adapterpattern;
class VlcPlayer implements AdvancedPlaying {
@Override
public AdvancedPlaying playVlc(String fileName) {
System.out.println("Đang phát tệp Vlc. Tên tệp: " + fileName);
return this;
}
@Override
public AdvancedPlaying playMp4(String fileName) {
// do nothing
return this;
}
}
adapterpattern/Mp4Player.java
package adapterpattern;
public class Mp4Player implements AdvancedPlaying {
@Override
public AdvancedPlaying playVlc(String fileName) {
// do nothing
return this;
}
@Override
public AdvancedPlaying playMp4(String fileName) {
System.out.println("Đang phát tệp Mp4. Tên tệp: " + fileName);
return this;
}
}
Bước 3
Tạo class MediaAdapter
triển khai MediaPlaying
.
adapterpattern/MediaAdapter.java
package adapterpattern;
public class MediaAdapter implements MediaPlaying {
AdvancedPlaying advancedPlayer;
public MediaAdapter(String mediaType) {
advancedPlayer = initPlayer(mediaType);
}
private AdvancedPlaying initPlayer(String mediaType) {
if (mediaType.equalsIgnoreCase("vlc")) return new VlcPlayer();
if (mediaType.equalsIgnoreCase("mp4")) return new Mp4Player();
else return null;
}
@Override
public MediaPlaying play(
String mediaType,
String fileName
) {
if (mediaType.equalsIgnoreCase("vlc")) {
advancedPlayer.playVlc(fileName);
return this;
}
if (mediaType.equalsIgnoreCase("mp4")) {
advancedPlayer.playMp4(fileName);
return this;
}
else {
System.out.println("Định dạng không được hỗ trợ");
return this;
}
}
}
Bước 4
Tạo class MediaPlayer
triển khai MediaPlaying
.
adapterpattern/MediaPlayer.java
package adapterpattern;
public class MediaPlayer implements MediaPlaying {
@Override
public MediaPlaying play(
String mediaType,
String fileName
) {
if (mediaType.equalsIgnoreCase("mp3")) {
System.out.println("Đang phát tệp Mp3. Tên tệp: " + fileName);
return this;
}
if (
mediaType.equalsIgnoreCase("vlc") ||
mediaType.equalsIgnoreCase("mp4")
) {
MediaAdapter adapter = new MediaAdapter(mediaType);
adapter.play(mediaType, fileName);
return this;
}
else {
System.out.println("Kiểu tệp không hợp lệ. Định dạng `" + mediaType + "` không được hỗ trợ.");
return this;
}
}
}
Bước 5
Sử dụng MediaPlayer
để phát các định dạng tệp khác nhau.
PatternDemo.java
import adapterpattern.MediaPlayer;
public class PatternDemo {
public static void main(String[] args) {
MediaPlayer player = new MediaPlayer();
player.play("mp3", "beyond the horizon.mp3")
.play("mp4", "alone.mp4")
.play("vlc", "far far away.vlc")
.play("avi", "mind me.avi");
}
}
Bước 6
Kiểm chứng lại kết quả in ra ở console
.
Đang phát tệp Mp3. Tên tệp: beyond the horizon.mp3
Đang phát tệp Mp4. Tên tệp: alone.mp4
Đang phát tệp Vlc. Tên tệp: far far away.vlc
Kiểu tệp không hợp lệ. Định dạng `avi` không được hỗ trợ.
Bridge Pattern
Bridge được sử dụng khi chúng ta cần tách một abstract
khỏi class
thực thể và khiến cho cả 2 đều trở nên linh động. Bridge được xếp vào nhóm các pattern Kiến Trúc.
Bridge sử dụng một giao diện đóng vai trò như một cầu nối giúp cho các chức năng của class
thực thể trở nên độc lập và tách riêng khỏi các class
tạo giao diện. Lúc này, cả 2 loại class
đều có thể được chỉnh sửa linh động mà không ảnh hưởng lẫn nhau.
Áp dụng triển khai
Ở đây chúng ta có một phần mềm vẽ các hình phẳng 2D có màu sắc khác nhau. Thông thường để đơn giản thì chúng ta chỉ cần có 01 class base
là Circle
và tạo ra các object
hình tròn có thuộc tính màu sắc khác nhau để vẽ. Lúc này, giao diện được sử dụng để vẽ là phương thức draw()
của class Circle
.
Tuy nhiên lúc này vấn đề nảy sinh là nếu như chúng ta muốn thay đổi cách hoạt động của draw()
, thì việc chỉnh sửa sẽ phải thực hiện trực tiếp trên class Circle
. Hoặc ví dụ như chúng ta có ý định chỉnh sửa class Circle
thì cũng phải coi chừng cái thuộc tính màu sắc kẻo ảnh hưởng đến draw()
.
Để tách rời giao diện draw()
và Circle
, một bridge
được tạo ra bởi interface Drawing
. Các class
triển khai DrawingRed
và DrawingGreen
sẽ được Circle
ủy thác hoàn toàn tác vụ draw()
chi tiết. Như vậy, giờ đây chúng ta có thể yên tâm thực hiện những điều chỉnh cần thiết trên Circle
mà không gây ảnh hưởng đến giao diện draw()
đang hoạt động và ngược lại.
Bước 1
Tạo interface bridge
.
bridgepattern/drawingapi/Drawing.java
package bridgepattern.drawingapi;
public interface Drawing {
public void draw(int x, int y, int radius);
}
Bước 2
Tạo các class
triển khai Drawing
.
bridgepattern/drawingapi/DrawingGreen.java
package bridgepattern.drawingapi;
public class DrawingGreen implements Drawing {
@Override
public void draw (int radius, int x, int y) {
System.out.println("=== Đang vẽ hình tròn..");
System.out.println("Màu sắc: Xanh lá");
System.out.println("Tọa độ tâm: (" + x + ", " + y + ")");
System.out.println("Bán kính: " + radius);
}
}
bridgepattern/drawingapi/DrawingRed.java
package bridgepattern.drawingapi;
public class DrawingRed implements Drawing {
@Override
public void draw(int x, int y, int radius) {
System.out.println("=== Đang vẽ hình tròn..");
System.out.println("Màu sắc: Đỏ");
System.out.println("Tọa độ tâm: (" + x + ", " + y + ")");
System.out.println("Bán kính: " + radius);
}
}
Bước 3
Tạo abstract Shape
sử dụng giao diện Drawing
.
bridgepattern/Shape.java
package bridgepattern;
import bridgepattern.drawingapi.Drawing;
abstract public class Shape {
protected Drawing drawingAPI;
protected Shape(Drawing api) {
drawingAPI = api;
}
public abstract void draw();
}
Bước 4
Tạo class Circle
mở rộng Shape
.
bridgepattern/Circle.java
package bridgepattern;
import bridgepattern.drawingapi.Drawing;
public class Circle extends Shape {
private int x, y, radius;
public Circle(int x, int y, int radius, Drawing api) {
super(api);
this.x = x;
this.y = y;
this.radius = radius;
}
@Override
public void draw() {
drawingAPI.draw(x, y, radius);
}
}
Bước 5
Sử dụng Shape
và các class Drawing
để vẽ các hình tròn với màu sắc khác nhau.
PatternDemo.java
import bridgepattern.Circle;
import bridgepattern.drawingapi.DrawingGreen;
import bridgepattern.drawingapi.DrawingRed;
import bridgepattern.Shape;
public class PatternDemo {
public static void main(String[] args) {
Shape circleRed = new Circle(100, 100, 10, new DrawingRed());
circleRed.draw();
Shape circleGreen = new Circle(100, 100, 10, new DrawingGreen());
circleGreen.draw();
}
}
Bước 6
Kiểm chứng lại kết quả in ra ở console
.
=== Đang vẽ hình tròn..
Màu sắc: Đỏ
Tọa độ tâm: (100, 100)
Bán kính: 10
=== Đang vẽ hình tròn..
Màu sắc: Xanh lá
Tọa độ tâm: (100, 10)
Bán kính: 100
Filter Pattern
Filter còn có tên gọi khác là Criteria. Cả 2 cách gọi này đều có nghĩa là sàng lọc hay màng lọc. Filter được xếp vào nhóm các pattern Kiến Trúc.
Áp dụng triển khai
- Chúng ta có 1 danh sách người dùng được tạo bởi
class Person
. - Để lọc danh sách này theo những tiêu chí khác nhau, chúng ta tạo ra một giao diện
Filter
và cácclass
triển khai. - Code
main
tạiPatternDemo
sẽ sử dụng các objectFilter
để lọc danh sáchList<Person>
theo các tiêu chí.
Bước 1
Tạo class Person
để mô tả người dùng.
filterperson/Person.java
package filterperson;
public class Person {
private String name;
private String gender;
private String status;
public Person(
String name,
String gender,
String status
) {
this.name = name;
this.gender = gender;
this.status = status;
}
public String getName() {
return name;
}
public String getGender() {
return gender;
}
public String getStatus() {
return status;
}
}
Bước 2
Tạo giao diện Filter
.
filterperson/Filter.java
package filterperson;
import java.util.List;
public interface Filter {
public List<Person> match(List<Person> personList);
}
Bước 3
Tạo các class
triển khai giao diện Filter
.
filterperson/FilterMale.java
package filterperson;
import java.util.ArrayList;
import java.util.List;
public class FilterMale implements Filter {
@Override
public List<Person> match(List<Person> personList) {
List<Person> males = new ArrayList<Person>();
for (Person p : personList)
if (p.getGender().equalsIgnoreCase("male"))
males.add(p);
return males;
}
}
filterperson/FilterFemale.java
package filterperson;
import java.util.ArrayList;
import java.util.List;
public class FilterFemale implements Filter {
@Override
public List<Person> match(List<Person> personList) {
List<Person> females = new ArrayList<Person>();
for (Person p : personList)
if (p.getGender().equalsIgnoreCase("female"))
females.add(p);
return females;
}
}
filterperson/FilterSingle.java
package filterperson;
import java.util.ArrayList;
import java.util.List;
public class FilterSingle implements Filter {
@Override
public List<Person> match(List<Person> personList) {
List<Person> singles = new ArrayList<Person>();
for (Person p : personList)
if (p.getStatus().equalsIgnoreCase("single"))
singles.add(p);
return singles;
}
}
filterperson/FilterAnd.java
package filterperson;
import java.util.List;
public class FilterAnd implements Filter {
private Filter firstFilter;
private Filter secondFilter;
public FilterAnd(
Filter firstFilter,
Filter secondFilter
) {
this.firstFilter = firstFilter;
this.secondFilter = secondFilter;
}
@Override
public List<Person> match(List<Person> personList) {
List<Person> firstMatched = firstFilter.match(personList);
return secondFilter.match(firstMatched);
}
}
filterperson/FilterOr.java
package filterperson;
import java.util.List;
public class FilterOr implements Filter {
private Filter firstFilter;
private Filter secondFilter;
public FilterOr(
Filter firstFilter,
Filter secondFilter
) {
this.firstFilter = firstFilter;
this.secondFilter = secondFilter;
}
@Override
public List<Person> match(List<Person> personList) {
List<Person> firstMatched = firstFilter.match(personList);
List<Person> secondMatched = secondFilter.match(personList);
for (Person p : secondMatched)
if (! firstMatched.contains(p))
firstMatched.add(p);
return firstMatched;
}
}
Bước 4
Sử dụng Shape
và các class Drawing
để vẽ các hình tròn với màu sắc khác nhau.
PatternDemo.java
import filterperson.*;
import java.util.ArrayList;
import java.util.List;
public class PatternDemo {
public static void main(String[] args) {
List<Person> personList = new ArrayList<Person>();
personList.add(new Person("Robert","Male", "Single"));
personList.add(new Person("John", "Male", "Married"));
personList.add(new Person("Laura", "Female", "Married"));
personList.add(new Person("Diana", "Female", "Single"));
personList.add(new Person("Mike", "Male", "Single"));
personList.add(new Person("Bobby", "Male", "Single"));
Filter filterMale = new FilterMale();
Filter filterFemale = new FilterFemale();
Filter filterSingle = new FilterSingle();
Filter filterSingleAndMale = new FilterAnd(filterSingle, filterMale);
Filter filterSingleOrFemale = new FilterOr(filterSingle, filterFemale);
System.out.println("MALE:");
print(filterMale.match(personList));
System.out.println("FEMALE:");
print(filterFemale.match(personList));
System.out.println("SINGLE and MALE:");
print(filterSingleAndMale.match(personList));
System.out.println("SINGLE or FEMALE");
print(filterSingleOrFemale.match(personList));
}
public static void print(List<Person> personList) {
for (Person p : personList) {
System.out.println(
"[ Name: " + p.getName() +
", Gender: " + p.getGender() +
", Status: " + p.getStatus() + " ]"
);
}
}
}
Bước 5
Kiểm chứng lại kết quả in ra ở console
.
MALE:
[ Name: Robert, Gender: Male, Status: Single ]
[ Name: John, Gender: Male, Status: Married ]
[ Name: Mike, Gender: Male, Status: Single ]
[ Name: Bobby, Gender: Male, Status: Single ]
FEMALE:
[ Name: Laura, Gender: Female, Status: Married ]
[ Name: Diana, Gender: Female, Status: Single ]
SINGLE and MALE:
[ Name: Robert, Gender: Male, Status: Single ]
[ Name: Mike, Gender: Male, Status: Single ]
[ Name: Bobby, Gender: Male, Status: Single ]
SINGLE or FEMALE
[ Name: Robert, Gender: Male, Status: Single ]
[ Name: Diana, Gender: Female, Status: Single ]
[ Name: Mike, Gender: Male, Status: Single ]
[ Name: Bobby, Gender: Male, Status: Single ]
[ Name: Laura, Gender: Female, Status: Married ]
Composite Pattern
Composite được hiểu nôm na là tổng hợp. Composite được sử dụng khi chúng ta muốn làm việc với một nhóm các object
như một object
đơn duy nhất. Composite được xếp vào nhóm các pattern Kiến Trúc.
Composite tạo ra một class
có chứa nhóm các object
của chính class
đó và đồng thời cung cấp các phương thức để làm việc với nhóm các object
này.
Áp dụng triển khai
- Chúng ta có một phần mềm mô tả kiến trúc nhân sự của một cửa hàng.
01 class Employee
được tạo ra để mô tả thực thể nhân viên.- Mỗi
object Employee
cũng có thể chứa danh sách các nhân viên cấp thấp hơn. - Code
main
trongPatternDemo
sẽ sử dụngclass Employee
để tạo một kiến trúc nhân sự và in ra danh sách tất cả các nhân viên.
Bước 1
Tạo class Employee
.
compositepattern/Employee.java
package compositepattern;
import java.util.ArrayList;
import java.util.List;
public class Employee {
private String name;
private String role;
private int salary;
private List<Employee> subordinateList;
public Employee(
String name,
String role,
int salary
) {
this.name = name;
this.salary = salary;
this.role = role;
this.subordinateList = new ArrayList<Employee>();
}
public void addSub(Employee emp) {
subordinateList.add(emp);
}
public void removeSub(Employee emp) {
subordinateList.remove(emp);
}
public List<Employee> getSubList() {
return subordinateList;
}
public String toString() {
return "Employee: [ Name: " + name + ", Role: " + role + ", Salary : " + salary+" ]";
}
}
Bước 2
Sử dụng class Employee
để tạo và in danh sách nhân viên.
PatternDemo.java
import compositepattern.Employee;
public class PatternDemo {
public static void main(String[] args) {
Employee CEO = new Employee("John","CEO", 30000);
Employee headSales = new Employee("Robert","Head Sales", 20000);
CEO.addSub(headSales);
Employee headMarketing = new Employee("Micheal","Head Marketing", 20000);
CEO.addSub(headMarketing);
Employee sale1 = new Employee("Richard","Sales", 10000);
headSales.addSub(sale1);
Employee sale2 = new Employee("Rob","Sales", 10000);
headSales.addSub(sale2);
Employee clerk1 = new Employee("Laura","Marketing", 10000);
headMarketing.addSub(clerk1);
Employee clerk2 = new Employee("Ryan","Marketing", 10000);
headMarketing.addSub(clerk2);
// in danh sách tất cả các nhân viên
System.out.println(CEO);
for (Employee headEmployee : CEO.getSubList()) {
System.out.println(headEmployee);
for (Employee employee : headEmployee.getSubList()) {
System.out.println(employee);
}
}
}
}
Bước 3
Kiểm chứng lại kết quả được in ra ở console
.
Employee: [ Name: John, Role: CEO, Salary : 30000 ]
Employee: [ Name: Robert, Role: Head Sales, Salary : 20000 ]
Employee: [ Name: Richard, Role: Sales, Salary : 10000 ]
Employee: [ Name: Rob, Role: Sales, Salary : 10000 ]
Employee: [ Name: Micheal, Role: Head Marketing, Salary : 20000 ]
Employee: [ Name: Laura, Role: Marketing, Salary : 10000 ]
Employee: [ Name: Ryan, Role: Marketing, Salary : 10000 ]
Decorator Pattern
Decorator cho phép chúng ta bổ sung thêm chức năng mới vào một object
đã tồn tại trước đó mà không cần thực hiện chỉnh sửa can thiệp vào kiến trúc của object
đó. Decorator được xếp vào nhóm các pattern Kiến Trúc.
Decorator tạo ra một class
vỏ bọc bao quanh class
nguyên bản và cung cấp thêm các chức năng mở rộng, đồng thời duy trì tính đặc trưng chặt chẽ của các phương thức đã có.
Áp dụng triển khai
Ở đây chúng ta có một phần mềm vẽ các hình phẳng 2D với các class
cơ bản là Circle
và Square
để vẽ các hình tròn và hình vuông. Bây giờ nhu cầu phát sinh là chúng ta muốn vẽ thêm đường viền cho các hình này, nhưng lại không muốn chỉnh sửa 2 class
ban đầu.
Do đó chúng ta sẽ tạo ra một abstract Decorator
bao quanh các object
hình học nguyên bản. Sau đó class Bordered
sẽ triển khai Decorator
và thêm vào khả năng vẽ đường viền.
Cuối cùng là code main
trong PatternDemo
sẽ sử dụng các object Decorator
để vẽ hình.
Bước 1
Tạo abstract Shape
.
decoratorpatter/Shape.java
package decoratorpattern;
public abstract class Shape {
public abstract void draw();
}
Bước 2
Tạo 2 class
hình học nguyên bản Circle
và Square
.
decoratorpatter/Circle.java
package decoratorpattern;
public class Circle extends Shape {
@Override
public void draw() {
System.out.println("Shape: Circle");
}
}
decoratorpatter/Square.java
package decoratorpattern;
public class Square extends Shape {
@Override
public void draw() {
System.out.println("Shape: Square");
}
}
Bước 3
Tạo abstract Decorator
.
decoratorpatter/Decorator.java
package decoratorpattern;
public abstract class Decorator extends Shape {
protected Shape origin;
public Decorator(Shape origin) {
this.origin = origin;
}
@Override
public void draw() {
origin.draw();
}
}
Bước 4
Tạo class Bordered
mở rộng Decorator
.
decoratorpatter/Bordered.java
package decoratorpattern;
public class Bordered extends Decorator {
public Bordered(Shape origin) {
super(origin);
}
@Override
public void draw() {
origin.draw();
setBorder();
}
private void setBorder() {
System.out.println("Bordered color: Red");
}
}
Bước 5
Sử dụng các object Decorator
để vẽ hình.
PatternDemo.java
import decoratorpattern.Circle;
import decoratorpattern.Shape;
import decoratorpattern.Bordered;
import decoratorpattern.Square;
public class PatternDemo {
public static void main(String[] args) {
Shape circle = new Circle();
System.out.println("=== Circle with normal border");
circle.draw();
Shape borderedCircle = new Bordered(new Circle());
System.out.println("=== Circle with red border");
borderedCircle.draw();
Shape borderedSquare = new Bordered(new Square());
System.out.println("=== Square with red border");
borderedSquare.draw();
}
}
Bước 6
Kiểm chứng lại kết quả được in ra ở console
.
=== Circle with normal border
Shape: Circle
=== Circle with red border
Shape: Circle
Bordered color: Red
=== Square with red border
Shape: Square
Bordered color: Red
Facade Pattern
Facade được tạm hiểu là mặt tiền, một interface
được tạo ra để giao tiếp với code client bên ngoài và ẩn đi hết những thứ phức tạp của một hệ thống phía sau. Facade được xếp vào nhóm các pattern Kiến Trúc.
Áp dụng triển khai
Ở đây chúng ta có một phần mềm vẽ các hình phẳng 2D với các class
mô tả thực thể là Circle
, Triangle
, và Square
. Một giao diện mặt tiền có tên là Painter
được tạo ra để ẩn đi các logic bên trong package
. Cuối cùng là code main
trên PatternDemo
sẽ sử dụng Painter
để yêu cầu hình vẽ mà không cần biết tới các class
mô tả thực thể ban đầu.
Bước 1
Tạo abstract Shape
.
facadepattern/Shape.java
package facadepattern;
public abstract class Shape {
public abstract void draw();
}
Bước 2
Tạo các class
mô tả thực thể hình học 2D.
facadepattern/Circle.java
package facadepattern;
class Circle extends Shape {
@Override
public void draw() {
System.out.println("Circle::draw()");
}
}
facadepattern/Triangle.java
package facadepattern;
class Triangle extends Shape {
@Override
public void draw() {
System.out.println("Triangle::draw()");
}
}
facadepattern/Square.java
package facadepattern;
class Square extends Shape {
@Override
public void draw() {
System.out.println("Square::draw()");
}
}
Bước 3
Tạo giao diện mặt tiền Painter
.
facadepattern/Painter.java
package facadepattern;
public class Painter {
private Shape circle;
private Shape triangle;
private Shape square;
public Painter() {
circle = new Circle();
triangle = new Triangle();
square = new Square();
}
public void drawCircle() {
circle.draw();
}
public void drawTriangle() {
triangle.draw();
}
public void drawSquare() {
square.draw();
}
}
Bước 4
Sử dụng giao diện Painter
để gửi yêu cầu vẽ các hình 2D.
PatternDemo.java
import facadepattern.Painter;
public class PatternDemo {
public static void main(String[] args) {
Painter painter = new Painter();
painter.drawCircle();
painter.drawTriangle();
painter.drawSquare();
}
}
Bước 5
Kiểm chứng lại kết quả được in ra tại console
.
Circle::draw()
Triangle::draw()
Square::draw()
Flyweight Pattern
Flyweight được sử dụng chủ yếu để giảm thiểu số lượng object
cần được khởi tạo để tiết kiệm bộ nhớ đệm và cải thiện hiệu năng hoạt động của phần mềm. Flyweight được xếp vào nhóm các pattern Kiến Trúc.
Flyweight luôn cố gắng tái sử dụng những object
cùng loại đã tồn tại trước đó và chỉ khởi tạo object
mới khi không tìm thấy object
nào phù hợp.
Áp dụng triển khai
Ở đây chúng ta có một phần mềm vẽ các hình tròn 2D. Phần mềm này sẽ vẽ khoảng 20 hình tròn có màu sắc và vị trí khác nhau. Tuy nhiên nhờ có Flyweight, chúng ta sẽ chỉ cần tạo ra 5 object
mô tả thực thể hình tròn tương ứng với 5 màu sắc khả dụng.
Chúng ta có class
mô tả thực thể Circle
và sau đó là Factory
được định nghĩa để thực hiện công việc khởi tạo các object Circle
khi cần thiết. Tuy nhiên, ở đây Factory
có một HashMap
để lưu lại những object Circle
đã từng khởi tạo với mỗi màu sắc. Do đó, khi có yêu cầu khởi tạo một object Circle
với một màu sắc cụ thể, Factory
sẽ kiểm tra xem trước đó đã tạo ra thứ tương tự hay chưa. Nếu như có object
phù hợp được tìm thấy trong HashMap
đã lưu trữ thì sẽ trả về object
đó, còn nếu như chưa có thì khởi tạo object
mới và đồng thời lưu lại.
Bước 1
Tạo class Circle
mô tả thực thể hình tròn 2D.
flyweightpatter/Circle.java
package flyweightpattern;
public class Circle {
private String color;
private int x;
private int y;
private int r;
public Circle(String color) {
this.color = color;
}
public void setX(int x) {
this.x = x;
}
public void setY(int y) {
this.y = y;
}
public void setR(int r) {
this.r = r;
}
public void draw() {
System.out.println(
"Circle::Draw() [ Color: " + color +
", x: " + x + ", y: " + y +
", radius: " + r + " ]"
);
}
}
Bước 2
Tạo Factory
thực hiện công việc khởi tạo các object Circle
.
flyweightpatter/Factory.java
package flyweightpattern;
import java.util.HashMap;
public class Factory {
private static final HashMap<String, Circle> circleMap = new HashMap<String, Circle>();
public static Circle getCircle(String color) {
if (circleMap.containsKey(color)) {
return circleMap.get(color);
}
else {
System.out.println("Creating circle of color: " + color);
Circle c = new Circle(color);
circleMap.put(color, c);
return c;
}
}
}
Bước 3
Sử dụng Factory
để yêu cầu lấy các object Circle
bằng cách truyền vào thông tin về màu sắc.
PatternDemo.java
import flyweightpattern.Circle;
import flyweightpattern.Factory;
public class PatternDemo {
private static final String colorList[] = { "Red", "Green", "Blue", "White", "Black" };
public static void main(String[] args) {
for (int i=0; i<20; i++) {
Circle c = Factory.getCircle(randomCollor());
c.setX(randomX());
c.setY(randomY());
c.setR(100);
c.draw();
}
}
private static String randomCollor() {
return colorList[(int) (Math.random() * colorList.length)];
}
private static int randomX() {
return (int) (Math.random() * 100);
}
private static int randomY() {
return (int) (Math.random() * 100);
}
}
Bước 4
Kiểm chứng lại kết quả được in ra ở console
.
Creating circle of color: Red
Circle::Draw() [ Color: Red, x: 20, y: 91, radius: 100 ]
Circle::Draw() [ Color: Red, x: 93, y: 33, radius: 100 ]
Creating circle of color: White
Circle::Draw() [ Color: White, x: 49, y: 40, radius: 100 ]
Creating circle of color: Green
Circle::Draw() [ Color: Green, x: 82, y: 60, radius: 100 ]
Circle::Draw() [ Color: White, x: 27, y: 73, radius: 100 ]
Creating circle of color: Black
Circle::Draw() [ Color: Black, x: 66, y: 5, radius: 100 ]
Circle::Draw() [ Color: Red, x: 71, y: 69, radius: 100 ]
Circle::Draw() [ Color: White, x: 39, y: 87, radius: 100 ]
Circle::Draw() [ Color: Black, x: 82, y: 25, radius: 100 ]
Circle::Draw() [ Color: Green, x: 24, y: 69, radius: 100 ]
Circle::Draw() [ Color: Black, x: 83, y: 17, radius: 100 ]
Circle::Draw() [ Color: Red, x: 60, y: 27, radius: 100 ]
Circle::Draw() [ Color: Black, x: 48, y: 73, radius: 100 ]
Circle::Draw() [ Color: Red, x: 99, y: 10, radius: 100 ]
Circle::Draw() [ Color: Red, x: 86, y: 74, radius: 100 ]
Creating circle of color: Blue
Circle::Draw() [ Color: Blue, x: 94, y: 85, radius: 100 ]
Circle::Draw() [ Color: White, x: 87, y: 51, radius: 100 ]
Circle::Draw() [ Color: Red, x: 82, y: 81, radius: 100 ]
Circle::Draw() [ Color: White, x: 94, y: 2, radius: 100 ]
Circle::Draw() [ Color: White, x: 75, y: 74, radius: 100 ]
Tham khảo:
- Design Patterns in Java Tutorial
- Hướng Dẫn Cơ Bản Design Patterns - 23 Dạng Thức Triển Khai Trong OOP
- Giới thiệu Design Patterns
- Nhập môn Design Pattern (Phong cách kiếm hiệp)
- Tổng quan về Design Pattern
- https://tuanitpro.com/design-pattern-la-gi/
- https://blog.duyet.net/2015/02/oop-design-patterns-la-gi.html
- https://refactoring.guru/design-patterns/catalog
- https://www.ibm.com/developerworks/java/tutorials/j-patterns/j-patterns.html
- https://www.journaldev.com/1827/java-design-patterns-example-tutorial
- https://viblo.asia/tags/design-pattern
- Lập trình cho interface chứ không phải để implement interface đó.
- Ưu tiên object composition (chứa trong) hơn là inheritance (thừa kế).