Design Pattern

1. Singleton

Giải pháp cho vấn đề tạo duy nhất một thể hiện đối tượng trong hệ thống

2. Builder

Giải pháp cho việc thiết kế một đối tượng có quá nhiều tham số

3. Factory

Giải pháp cho việc tạo một nơi để chuyên khởi tạo đối tượng dựa vào 1 tham số type

--> Mỗi khi có một đối tượng mới, chúng ta cần phải tạo Class đối tượng mới đó và Thêm code trong class Factory để tạo đối tượng mới đó --> bad design
chỉ phù hợp cho một tập hợp nhỏ các đối tượng cần khởi tạo

4. Abstract factory

Giải pháp cho việc tạo ra đối tượng mới,
Việc cần làm là tạo một Class implement AbstractFactory mà tạo ra đối tượng mới đó.

Do đó mỗi khi có đối tượng mới, chúng ta chỉ cần đơn giản là tạo 1 Class về đối tượng đó, và tạo một Class implement AbstractFactoryClass

Do đó chúng ta không phải chỉnh sửa code, mà chỉ thêm mới code vào Source

Great design

5. Proxy

Nó là một thiết kế mà tập trung vào việc truy cập một đối tượng thông qua một Proxy object
Interface Image{

     showImage()

}
RealImage implemnt Image{

            construct(){

                    loadImage()

            }

            showImage()

}
Proxy impl Image{

           RealImage realImg;

        

            showImg(){

                      realImg.showImg()

            }

}

6. Strategy

Giải pháp cho việc trừu tượng hóa đối tượng sử dụng
ví dụ chúng ta có một hàm  tìm kiếm trừu tượng(InterfaceSearch)

nhưng tìm kiếm điều gì thì cần phải truyền vào Implement của InterfaceSearch đó

ví dụ chúng ta có 1 list Apple có các thuộc tính màu sắc, trọng lượng, thương hiệu, loại , thời gian hái,

Vấn đề là cần phải viết hàm tìm kiếm ra một tập táo có màu sắc là Đỏ
-- Viết 1 hàm findRedApples(List apples): Hàm này thực hiện tìm if(apple.color =="red")

Điều gì nếu hôm sau tôi cần tìm một Set táo có trọng lượng > 3

-- Developer viết thêm một hàm tìm táo  findAppleByWeight(List apple, int weight)

....

So confuse

--> giải pháp là Tạo một hàm
findApple(Criteria criteria){

     appleList.forEach(
          if(apple.check()==true) then add apple to the returnList
     )
}
Criteria là 1 interface có hàm  boolean check()

mỗi một tiêu chí chúng ta sẽ tạo một Class impl Criteria và thực hiện hàm
boolean check(Apple apple) {
  if(apple match criteria)
     return true;

  else return false;
}
theo tiêu chí cần.


Great design: Không phải sửa code hàm findApple, hoặc không phải viết thêm hàm vào 1 class
Chỉ cần tạo một class mới  và test Class đó
--> Đảm bảo tính modul đóng gói, không sửa gì có sẵn

7. State design pattern

hãy nghĩ về hàm này
controllMyTV(String state){

   if(state is ON) then turn on TV

   if (state is OFF) the turn off TV

}
Code chạy khá đẹp

Nhưng điều gì xảy ra nếu TV có thêm một tính năng RESTART

Chúng ta lại phải sửa code hàm controllMyTV check if(state is RESTART) the restart TV

--> Không ổn, lại phải test lại toàn bộ function

---> Giải pháp là tạo 1 interface cho State và một hàm action()

Khi đó hàm controllMyTV sẽ sửa lại như sau
controllMyTV(State state){

   state.action();

}


Như vậy mỗi một State chúng ta sẽ implement 1 class cho nó --> đảm bảo việc thêm mới Class thay vì sửa Class--> đảm bảo tính module đóng gói không sửa lại code

8. Visitor

Khi chúng ta có một Interface  Item và một tập các sản phẩm  đã impl Item

Vd: Máy giặt, máy hút bụi, nồi cơm điện ,....

Vấn đề là khi siêu thị có các chương trình giảm giá  tạm gọi là

Chương trình Tết: Giảm 50% Cho các mặt hàng điện thoại, 10% cho các mặt hàng gia dụng

Vậy chúng ta sẽ thiết kế hàm tính giá thế nào cho mỗi sản phẩm

---> Giải pháp là dùng Visitor Design pattern

interface ChuongTrinhVisitor{
       double visit(DienThoai item);
       double visit(GiaDung item);

}

class ChuongTrinhTetVisitorImpl impl ChuongTrinhVisitor{
         double visit( DienThoai item){
           if else here and return the price;
         }

         double visit( GiaDung item){
           if else here and return the price;
         }
}

----------
interface Item{
     double accept(ChuongTrinhVisitor visitor)
}

class DienThoai impl Item{
      double accept(ChuongTrinhVisitor visitor){
                  return visitor.visit(this);
        }

}

-------------
using

ChuongTrinhVisitor tet = new ChuongTrìnhTetVisitorImpl();

List<Item> = new ..., list.add ....

Foreach(list) then  total += list.get(i).accept(tet)

---------------------------------

Kết luận nếu có thêm một Visitor khác thì chúng ta chỉ việc impl ChuongTrinhVisitor và sử dụng
Nếu có sản phẩm mới thì chỉ việc tạo thêm một sản phẩm và thêm hàm trong các VisitorImpl Tương ứng

---------
Khi thêm sản phẩm mới thì đúng là ác mộng

Dẫn đến cần thiết kế lại :-)))

9. Template

Hiểu đúng theo nghĩa template thì nó là một thứ tự mẫu định sẵn nào đó mà nhiều đối tượng sẽ dùng chung một mẫu

ví dụ như xây một ngôi nhà thì
1. xây nền móng
2. xây trụ
3. xây tường
4. tạo cửa sổ
5. lợp mái
public abstract class HouseTemplate {

 //template method, final so subclasses can't override
 public final void buildHouse(){
  buildFoundation();
  buildPillars();
  buildWalls();
  buildWindows();
  System.out.println("House is built.");
 }

 //default implementation
 private void buildWindows() {
  System.out.println("Building Glass Windows");
 }

 //methods to be implemented by subclasses
 public abstract void buildWalls();
 public abstract void buildPillars();

 private void buildFoundation() {
  System.out.println("Building foundation with cement,iron rods and sand");
 }
}

Do vậy các class kế thừa abstract class sẽ ghi đè lại các phương thức và dùng hàm build để thực hiện template đã có sẵn

10. Propotype

Mục đích mẫu thiết kế để tạo ra một Class có khả năng được copy và tạo ra một đối tượng mới có cùng các giá trị thôucj tính

Do vậy chúng ta chỉ cần tạo một class sau đó implement Interface Cloneable 

Sau đó impl phương thức clone()

Return Super.clone()


11. Adapter

Là một mẫu thiết kế đúng như tên của nó

Hãy nghĩ về nó như một giải pháp đáp ứng cho Client

Ví dụ một trang trại có nuôi các loài vật sau: Gấu, Thỏ, Đại bàng, Cá

Ông ấy tạo ra các lớp
class Gấu{

   int speedDichuyen()
}
class Thỏ{
   int speedNhảy()
}

class DaiBang{
  int speedBay()
}
class Cá{
  int speedBoi()
}
và ông muốn viết một chương trình để tính tốc độ cho từng loài vật mà ông đang nuôi

nếu viết hàm thông thường

thì ông sẽ phải
class App{
    int tinhTocdoTho(Tho tho){
            return tho.speedNhay();
    }
.....
// tương tự các loài khác
}

như vậy sẽ rất khó cho việc maintain code trong tương lai, vì có thể có nhiều loài vậy mới ra nhập trang trại, và ông sẽ phải sửa lại hàm App và viết thêm function tính tốc độ cho loài đó

thay vì viết hàm như vậy

Ông sẽ viết một interface apdapter
interface AdapterSpeed{
      int speed();
}
Sau đó ông sẽ viết một class impl cho các loài
class  AdapterSpeedTho impl AdapterSpeed{

     Tho tho;

     AdapterSpeedTho(Tho tho);
   
     int speed(){
        return tho.speedNhay();
     }
}
Class App{

      int tinhTocDo(AdapterSpeed adapter){
            adapter.speed();
      }
  
}

Các dùng
int speed = App.tinhTocDo( new AdapterSpeedTho(new Tho()));

Như vậy sau này có thêm các loài khác vào thì chúng ta chỉ cần định nghĩa loài đó rồi sau đó tạo thêm class Adapter cho loài đó là ok. Chúng ta không phải bổ sung hàm trong App class nữa.

--> Đảm bảo tính năng chỉ thêm mới code, không sửa code class có sẵn.

12.Facade 

Lấy một ví dụ cho dễ hiểu:
Giờ bạn có Một hãng hàng không Bamboo Airway, Vietjet Airway và Vietnam Airline

Nếu bạn muốn bay chuyến VietNam Airline thì cần phải code như sau
VietnamAirline flight = new VietnamAirline();
flight.goToPortVietnamAirline();
flight.checkTicket();
flight.fly();
Nếu bạn muốn bay Vietjet Air thì
VietjetAir vja= new VietjetAir();
vja.goToPortVietjetAir()
vja.fly();

Có nghĩa nếu bạn muốn bay một chuyến thì bạn phải biết các hàm trước đó thiết lập trước khi cất cánh fly();

Tuy nhiên là một Client() thì họ sẽ không biết làm thế nào để thực hiện từng bước một và bước nào trước bước nào sau

Facade pattern sinh ra để che giấu đi sự phức tạp đó
Tạo một class Facade{
      flyVietNamAirline(){
         //copy các bước trên
      }

      flyVietJetAir(){
     // copy bên trên các bước vào đây

      }
}

Và Client sẽ chỉ dùng cái Facade này để gọi

---> Ok dễ hiểu, design này quá dễ hiểu

13. Composite

Để hiểu về mẫu pattern này thì chúng ta sẽ lấy ví dụ về một Game đua của một tập các động vật

Ví dụ có Ngựa, Trâu, Bò, Thỏ tham gia một cuộc đua

Chúng ta sẽ có một Interface chung
interface Racing{

   void prepare();  // set up time =0;

   void start();  // set up speed

   stop();   // caculate running time* speed and update quãng đường đã chạy

   showQuangDuong(); /// show quãng đường đã chạy
}
Sau đó chúng ta sẽ implement interface này cho các loài vật trên

class Tho implement Racing, Animal{
int time;

int spead;

int quangduong;

   void prepare();  // set up time =0;
   {  time=0;
   }                
   void start();  // set up speed
   {  speed= 5;
   }  
   stop();   // caculate running time* speed and update quãng đường đã chạy
   {  quangduong=(System.Time - time)*speed;
   }  
   showQuangDuong(); /// show quãng đường đã chạy
   {  print(Thỏ đã chạy đương quãng đường: quangduong);
   }  

}


}


Tại lớp App chúng ta cũng sẽ implement interface trên như sau
class App implement Racing{
   List<Animal> animalList = new AninalList()

  void addToList(Animal animal){
       animalList.add(animal);
   }
  
   // remove()


  void prepare(){
      forEach(animalList).each().prepare();
  }

  void start(){
      forEach(animalList).each().start();
  }


 void stop(){
      forEach(animalList).each().stop();
  }

 void showQuangDuong(){
      forEach(animalList).each().showQuangDuong();
  }

}


Để bắt đầu cuộc đua;
Chúng ta sẽ tạo
App app= new App();

// Tạo động vật tham gia ngựa
Animal ngua = new Ngưa();
app.add(ngua)

//Tạo thỏ
Animal tho= new Tho();
app.add(tho)

Tạo trâu
Animal trau= new Trau();
app.add(trau)

Tạo Bò
Animal bo= new Bo();
app.add(bo)


// chuẩn bị
app.prepare();
//start
app.start();
// rung 5s
Thread.sleep(5s);

//stop
app.stop();

//in ra quang duong da chay
app.showQuangDuong();


Vậy là chúng ta đã thiết kế xong game đua này.
Ưu điểm cách thiết kế này là khi có động vật mới thì chúng ta chỉ cần tạo Class động vật đó và Implement Racing interface

Sau đó Client có thể dùng App để add thêm động vật đó vào và tham gia cuộc đua

Việc này lại giúp bảo trì code tốt hơn không chỉnh  sửa class đã có mà chỉ việc tạo class mới


14. Flyweight

Về cơ bản mẫu thiết kế này gần giống với mẫu thiết kế Factory pattern

Nhưng khác ở chỗ là mẫu Factory pattern sẽ luôn trả về đối tượng mới dựa trên đối số truyền vào

Còn mẫu Flyweight sẽ lưu ý hơn cho những trường hợp người dùng chỉ muốn tạo mới đối tượng nếu nó không tồn tại và sẽ dùng lại đối tượng cũ nếu nó đã tồn tại

Cơ chế này gần giống như cơ chế cache.

Vậy vấn đề là gì: Mẫu đối tượng cần cache nó phải có những thuộc tính nội tại private mà khi khởi tạo đối tượng chúng ta sẽ setup, và những thuộc tính ngoại tại là cho phép Client setup lại để sử dụng, như vậy thì mẫu này mới có ích. vì việc dùng lại đối tượng đã tạo có thể gặp phải trường hợp là các thuộc tính ngoại tại đã bị thiết lập trước đó.

ví dụ

  public class ShapeFactory {
        // hashmap dùng để cache dữ liệu các đối tượng đã khởi tạo
        private static final HashMap<ShapeType,Shape> shapes = new HashMap<ShapeType,Shape>();
               
        public static Shape getShape(ShapeType type) {
               // lấy từ cache local
                Shape shapeImpl = shapes.get(type); 
               // nếu không có trong cache
                if (shapeImpl == null) {
                        if (type.equals(ShapeType.OVAL_FILL)) {
                         } else if (type.equals(ShapeType.OVAL_NOFILL)) {
                              shapeImpl = new Oval(false);
                         } else if (type.equals(ShapeType.LINE)) {
                                shapeImpl = new Line();
                          }
                        shapes.put(type, shapeImpl);
                }else{ // nếu có trong cache
                 shapes.reset() // làm mới lại các thuộc tính đã setup trước đó
               }
                   return shapeImpl;
          }
         public static enum ShapeType{
               OVAL_FILL,OVAL_NOFILL,LINE;
          }
 }


15. Bridge

Mẫu thiết kế này nó đến như ý nghĩa tên của nó là một cây cầu nối giữa 2 Interface

Từ trước tới giờ chúng ta chủ yếu apply 1 interface như là nhân vật chính cho các mô hình mẫu thiết kế.

nhưng mẫu thiết kế này sẽ đến như là một mô hình mà có 2 nhân vật chính kết hợp với nhau mà một interface sẽ gọi đến hàm một interface khác
interface Color(){
  void applyColor();
}


class RedColor impl Color(){
   void applyColor(){
        print("apply redColor");
   }
}
abstract class Shape{
 
  Color color;    // bridge here

  Shape(Color color){
     this.color = color;  
  }

  abstract void applyColor(Color color);  // link to Color's applyColor method

}

class Circle extend Shape{

    Circle(Color color){
        super(color);
   }

  void applyColor(Color color){
        color.applyColor();
  }

}
Done

16. Object Pool 


Chúng ta sẽ tạo một class như Pool là thùng chứa các đối tượng

chúng ta sẽ có 1 list các đối tượng đang sẵn sàng và một list các đối tượng không sẵn sàng
và có cơ chế xóa các đối tượng nếu nó vượt quá thời gian expire, cơ chế này sẽ giúp giảm tài nguyên lưu trữ nếu quá lâu rồi các object trong Pool không được sử dụng, nếu cứ nắm giữ chỗ cho chúng trong Pool thì quả là lãng phí.

Thuật toán getObjectPool như sau

Kiểu tra xem trong availableList có đối tượng Pool không
nếu không có thì tạo mới và put đối tượng vào inUsedList và return obj.
nếu có thì kiểm tra thời gian xem obj đã hết hạn chưa. nếu hết hạn thì remove obj khỏi availableList và tạo obj mới và put và inUsedList
nếu thời gian chưa hết hạn thì dùng luôn và put đối tượng vào inUsedList  để giữ object

Hàm releaseObject sẽ thực hiện loại bỏ object khỏi inUsedList và put vào availableList


Comments

Popular posts from this blog

Fixing the DeepSpeed Import Error While Fine-Tuning the Qwen Model

Amazon Linux 2023 - User data configuration for launch templates to connect to the EKS cluster

How to create ISM policy and rotate logs in opensearch