Sinh viên: Hoàng Viết Khánh
MSV: 20020296
Repo: https://github.com/kan01234/design-patterns
Lý thuyết: Design Patterns: Elements of Reusable Object-Oriented Software được viết bởi các tác giả Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides
Tham khảo: https://gpcoder.com/4164-gioi-thieu-design-patterns
Nhận xét chung: Vì đây là một repository có chủ đề về design pattern nên các file .java trong đó đều bám sát những nguyên tắc thiết kế được đề cập trong cuốn sách của Gang of Four.
23 folder chứa các file là ví dụ của 23 design pattern được liệt kê trong cuốn sách, chia làm 3 nhóm chính:
- Creational
- Structural
- Behavioral
Những Design pattern loại này cung cấp một giải pháp để tạo ra các object và che giấu được logic của việc tạo ra nó, thay vì tạo ra object một cách trực tiếp bằng cách sử dụng method new. Điều này giúp cho chương trình trở nên mềm dẻo hơn trong việc quyết định object nào cần được tạo ra trong những tình huống được đưa ra.
- Mỗi class chỉ khởi tạo duy nhất MỘT instance.
- Instance có thể được truy xuất một cách toàn cục bằng hàm public
getInstance()
.
Sơ đồ thiết kế:
Ví dụ:
public class SingletonObject {
private static SingletonObject instance = new SingletonGreed();
private SingletonObject() {
}
public static SingletonObject getInstance() {
return instance;
}
}
Nhận xét:
- Dễ cài đặt.
- Hiệu suất cao.
- Tránh xung đột giữa các luồng.
- Đúng nghĩa là 1 nhà máy (factory), sản xuất ra các sản phẩm (product).
- Tạo đối tượng mà không để lộ logic tạo cho người dùng, khi tham chiếu sẽ chiếu đến đối tượng mới được tạo ra bởi một
interface
chung. - Một Super-class được tạo ra, thường là
interface
hoặcabstract class
kế thừa bởi các sub-class khác nhau với những logic khác nhau tượng trưng cho nhứng sản phẩm có thể được tạo ra. - Một Factory class chịu trách nhiệm khởi tạo các sản phẩm dựa theo tham số đầu vào. Lưu ý: lớp này có thiết kế kiểu Singleton hoặc phải cung cấp một method dạng
public static
cho việc khởi tạo đối tượng, việc lựa chọn đối tượng có thể sử dụng cấu trúc phân nhánhif-else
hoặcswitch-case
.
Sơ đồ thiết kế:
Ví dụ:
Hamburger, interface that define the product
public interface Hamburger {
public void pack();
public boolean isVegetarian();
}
BeefHamburger, class that implement Hamburger, one of concrete products
public class BeefHamburger implements Hamburger {
@Override
public void pack() {
System.out.println("pack beef hamburger into paper box");
}
// other methods ....
}
FakeMeatHamburger, class that implment Hamburger, one of concrete products
public class FakeMeatHamburger implements Hamburger {
@Override
public void pack() {
System.out.println("pack fake meat hamburger with paper");
}
// other methods ....
}
SimpleHamburgerFactory, class that used to create concrete product and maintains factory method
public class SimpleHamburgerFactory {
public Hamburger makeHamburger(String order) {
switch (order) {
case "beef":
return new BeefHamburger();
case "fake meat":
return new FakeMeatHamburger();
default:
return null;
}
}
}
Trong đó:
- Hamburger là
abstract class
hoặcinterface
, quy định các hàm kế thừa từ chúng nhưng không quy định cụ thể các phương thức được kế thừa. - BeefHamburger và FakeMeatHamburger là những class kế thừa từ Hamburger, kế thừa những phương thức trừu tượng từ lớp cha nhưng có hành vi khác nhau.
- Nếu như cần tạo thêm loại Hamburger nào mới thì chỉ cần cập nhật phương thức factory.
Nhận xét:
- Giảm sự phụ thuộc vào module.
- Dễ dàng mở rộng code.
- Thống nhất về cách đặt tên.
- Vi phạm open-close principle (quy tắc đóng-mở) của lập trình hướng đối tượng.
- Tạo ra một Super-factory dùng để tạo ra các Factory khác.
- Abstract Factory là một pattern cấp cao hơn so với Factory Method.
- Một
interface
có nhiệm vụ tạo ra một Factory của các object có liên quan tới nhau mà không cần phải chỉ ra trực tiếp các class của object.
Sơ đồ thiết kế:
Ví dụ:
public interface Hamburger {
public void pack();
public boolean isVegetarian();
}
BeefHamburger
public class BeefHamburger implements Hamburger {
@Override
public void pack() {
System.out.println("pack beef hamburger into paper box");
}
@Override
public boolean isVegetarian() {
return false;
}
}
FakeMeatHamburger
public class FakeMeatHamburger implements Hamburger {
@Override
public void pack() {
System.out.println("pack fake meat hamburger with paper");
}
@Override
public boolean isVegetarian() {
return true;
}
}
Nugget
public interface Nugget {
public void fired();
public void pack();
public boolean isVegetarian();
}
ChickenNugget
public class ChickenNugget implements Nugget {
@Override
public void fired() {
System.out.println("fired chicken nugget for 6 minutes");
}
@Override
public void pack() {
System.out.println("pack 6 pieces of chicken nugget into box");
}
@Override
public boolean isVegetarian() {
return false;
}
}
FakeMeatNugget
public class FakeMeatNugget implements Nugget {
@Override
public void fired() {
System.out.println("fired fake meat nugget for 3 minutes");
}
@Override
public void pack() {
System.out.println("pack 9 pieces of fake meat nugget into box");
}
@Override
public boolean isVegetarian() {
return true;
}
}
FastFoodFactory
public interface FastFoodFactory {
public Hamburger createHamburger();
public Nugget createNugget();
}
NormalFastFoodFactory, class to create normal fast food
public class NormalFastFoodFactory implements FastFoodFactory {
@Override
public Hamburger createHamburger() {
return new BeefHamburger();
}
@Override
public Nugget createNugget() {
return new ChickenNugget();
}
}
VegetarianFastFoodFactory, class to create vegetarian fast food
public class VegetarianFastFoodFactory implements FastFoodFactory {
@Override
public Hamburger createHamburger() {
return new FakeMeatHamburger();
}
@Override
public Nugget createNugget() {
return new FakeMeatNugget();
}
}
Trong đó:
- Hamburger và Nugget là những
abstract class
hoặcinterface
, quy định các hàm kế thừa từ chúng nhưng không quy định cụ thể các phương thức được kế thừa. - BeefHamburger và FakeMeatHamburger là những class kế thừa từ Hamburger, ChickenNugget và FakeMeatNugget là những class kế thừa từ Nugget, kế thừa những phương thức trừu tượng từ lớp cha nhưng có hành vi khác nhau.
- FastFoodFactory cài đặt kiểu
interface
hoặcabstract class
. - NormalFastFoodFactory và VegetarianFastFoodFactory kế thừa từ FastFoodFactory, dùng để tạo ra các product (Hamburger và Nugget).
Nhận xét:
- Có các ưu nhược điểm của Factory, thậm chí linh hoạt hơn trong khả năng khởi tạo đối tượng.
- Nhược điểm: phức tạp hơn Factory.
- Xây dựng một đôi tượng phức tạp bằng cách sử dụng các đối tượng đơn giản.
- Xây dựng để khắc phục một số nhược điểm của Factory và Abstract Factory khi mà object có nhiều thuộc tính.
- Xử lí vấn đề bằng cách cung cấp một hàm khởi tạo với những tham số bắt buộc và các method
getter
/setter
để cài đặt các tham số tùy chọn.
Sơ đồ thiết kế:
Ví dụ:
public class Animal {
private String name;
private double weight;
private double height;
private String gender;
// getter, setter methods
}
AnimalBuilder, interface that represent to builder
public interface AnimalBuilder {
public AnimalBuilder setName(String name);
public AnimalBuilder setWeight(double weight);
public AnimalBuilder setHeight(double height);
public AnimalBuilder setGender(String gender);
public Animal build();
}
SmallDogBuilder, class that represent to concrete builder
public class SmallDogBuilder implements AnimalBuilder {
private String name;
private double weight;
private double height;
private String gender;
@Override
public Animal build() {
return new Animal()
.setName(name)
.setWeight(weight)
.setHeight(height)
.setGender(gender);
}
@Override
public AnimalBuilder setName(String name) {
this.name = name;
return this;
}
@Override
public AnimalBuilder setWeight(double weight) {
this.weight = weight;
return this;
}
@Override
public AnimalBuilder setHeight(double height) {
this.height = height;
return this;
}
@Override
public AnimalBuilder setGender(String gender) {
this.gender = gender;
return this;
}
}
Nhận xét:
- Code dễ đọc.
- Giảm lượng tham số truyền vào.
- Độ phức tạp về tổng thể tăng vì phải viết nhiều
concrete class
hơn và phải lặp lại code với mỗi class như vậy.
- Nó có nhiệm vụ khởi tạo một đối tượng bằng cách clone một đối tượng đã tồn tại thay vì khởi tạo với từ khoá new.
- Đối tượng mới là một bản sao có thể giống 100% với đối tượng gốc, chúng ta có thể thay đổi dữ liệu của nó mà không ảnh hưởng đến đối tượng gốc.
Sơ đồ thiết kế:
Ví dụ:
make it simple, just extends Cloneable class in Java
public interface Animal extends Cloneable {
public Animal makeCopy();
}
Sheep, Pig are ConcretePrototype
public class Sheep implements Animal {
public Sheep() {
System.out.println("new Sheep");
}
public Animal makeCopy() {
System.out.println("making a Sheep copy");
Sheep sheep = null;
try {
sheep = (Sheep) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return sheep;
}
}
Pig
public class Pig implements Animal {
public Pig() {
System.out.println("new Pig");
}
public Animal makeCopy() {
System.out.println("making a Pig copy");
Pig pig = null;
try {
pig = (Pig) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return pig;
}
}
Nhận xét:
- Giảm chi phí để tạo ra đối tượng mới.
- Giảm độ phức tạp cho việc khởi tạo.
- Giảm việc phân lớp, tránh việc tạo nhiều lớp con cho việc khởi tạo đối tượng như của Abstract Factory.
Những Design pattern loại này liên quan tới class và các thành phần của object. Nó dùng để thiết lập, định nghĩa quan hệ giữa các đối tượng.
- Cho phép các
interface
không liên quan tới nhau có thể làm việc cùng nhau bởi một đối tượng gọi là adapter. - Adapter giữ vai trò trung gian giữa hai lớp, chuyển đổi
interface
của một hay nhiều lớp có sẵn thành mộtinterface
khác, thích hợp cho lớp đang viết. - Điều này cho phép các lớp có các
interface
khác nhau có thể dễ dàng giao tiếp tốt với nhau thông quainterface
trung gian, không cần thay đổi code của lớp có sẵn cũng như lớp đang viết.
Sơ đồ thiết kế:
- Target: Lớp chứa các chức năng được sử dụng bởi Client.
- Adaptee: Lớp chưa tương thích, cần Adapter để tích hợp vào lớp Target.
- Adapter: Lớp trung gian, tích hợp các Adaptee.
Ví dụ:
- assume the panda can eat, sit and hello
public interface Panda {
public String eat();
public String sit();
public String hello();
}
- class represent to real Panda
public class PandaImpl implements Panda {
// override methods...
}
- class represent to part time worker
public class PartTimeWorker {
public String action(String action) {
return "part time work, " + action;
}
}
- class convert part time worker to Panda, need to implement the Panda methods
public class PandaAdapter implements Panda {
private PartTimeWorker partTimeWorker;
public PandaAdapter(PartTimeWorker partTimeWorker) {
this.partTimeWorker = partTimeWorker;
}
@Override
public String eat() {
return partTimeWorker.action("fake eating");
}
@Override
public String hello() {
return partTimeWorker.action("wave the hands");
}
@Override
public String sit() {
return partTimeWorker.action("sit like a king");
}
}
Nhận xét:
- Cho phép nhiều đối tượng có
interface
khác nhau có thể giao tiếp với nhau. - Đôi khi có quá nhiều Adapter được thiết kế trong một chuỗi Adapter (chain) trước khi đến được yêu cầu thực sự.
- Ý tưởng của Bridge 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.
Sơ đồ thiết kế:
Ví dụ:
ControllerFunction, represent to implementation
public interface ControllerFunction {
public void joystickMove(String direction);
public void pressHome();
}
PS4Function, represent to concrete implementation, class that implement PS4 functions
public class PS4Function implements ControllerFunction {
@Override
public void joystickMove(String direction) {
System.out.println("move " + direction);
}
@Override
public void pressHome() {
System.out.println("show PS4 home menu");
}
}
SteamFunction, represent to concrete implementation, class that implement Steam function
public class SteamFunction implements ControllerFunction {
@Override
public void joystickMove(String direction) {
System.out.println("pressed " + direction);
}
@Override
public void pressHome() {
System.out.println("unsupported button");
}
}
Controller, represent of abstraction
public interface Controller {
public void joystickMove(String direction);
public void pressHome();
}
PS4Controller, represent of ConcreteAbstraction, when create PS4Controller, it need to take a ControllerFunction. And when button pressed, it will call the ControllerFunction.
public class PS4Controller implements Controller {
private ControllerFunction controllerFunction;
public PS4Controller(ControllerFunction controllerFunction) {
this.controllerFunction = controllerFunction;
}
@Override
public void joystickMove(String direction) {
controllerFunction.joystickMove(direction);
}
@Override
public void pressHome() {
controllerFunction.pressHome();
}
}
Nhận xét:
- 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ễ bảo trì hơn.
- Dễ dàng mở rộng về sau.
- Cho phép ẩn các chi tiết implement từ client.
- Tổng hợp những thành phần có quan hệ với nhau để tạo ra thành phần lớn hơn.
- Composite được sử dụng khi chúng ta cần xử lý một nhóm đối tượng tương tự theo cách xử lý 1 object.
Sơ đồ thiết kế:
Ví dụ:
public abstract class Game {
public abstract void displayInfo();
}
GameItem
public class GameItem extends Game {
// other methods...
@Override
public void displayInfo() {
System.out.println("name: " + name + " | realase year: " + releaseYear + " | console: " + console);
}
}
GameType
public class GameType extends Game {
private List<Game> games;
// other methods...
@Override
public void displayInfo() {
System.out.println("----");
System.out.println("name: " + name);
System.out.println("desc:" + desc);
for (Game game : games)
game.displayInfo();
}
}
PS4Controller, represent of ConcreteAbstraction, when create PS4Controller, it need to take a ControllerFunction. And when button pressed, it will call the ControllerFunction.
public class PS4Controller implements Controller {
private ControllerFunction controllerFunction;
public PS4Controller(ControllerFunction controllerFunction) {
this.controllerFunction = controllerFunction;
}
@Override
public void joystickMove(String direction) {
controllerFunction.joystickMove(direction);
}
@Override
public void pressHome() {
controllerFunction.pressHome();
}
}
Trong đó:
- Game là Component tức là một
interface
hoặcabstract class
quy định các method chung cần phải có cho tất cả các thành phần tham gia vào mẫu này. - GameItem là Leaf là lớp hiện thực (implements) các phương thức của Component. Nó là các object không có con.
- GameType là Composite là tập hợp các Leaf và cài đăt các phương thức của Component.
Nhận xét:
- Cung cấp cùng một cách sử dụng đối với từng đối tượng riêng lẻ hoặc nhóm các đối tượng với nhau.