Design Pattern là gì? Áp dụng vào các dự án như thế nào ? (P1)



1. Design pattern là gì?

"Hỏi thế gian DS là chi, mà dân Dev thề nguyền sống chết". Design Patterns giống như những bí kíp võ công mà chỉ cần các cao thủ dev rèn luyện đến cảnh giới "Đăng phong tháo cực" thì có thể có một chỗ đứng vững chắc trên giang hồ. 

  • Design pattern là các giải pháp tổng thể đã được tối ưu hóa, được tái sử dụng cho các vấn đề phổ biến trong thiết kế phần mềm mà chúng ta thường gặp phải hàng ngày. Đây là tập các giải pháp đã được suy nghĩ, đã giải quyết trong tình huống cụ thể.

2. Tác dụng của Design Patterns là gì?

  • Nhờ có Design Patterns mà các dev có thể áp dụng để giải quyết nhiều cấn đề khác nhau một cách tương tự. Một số vấn đề mà các bạn gập phải, có thể nếu bạn đã có kinh nghiệm về lĩnh vực IT, bạn có thể nghĩ và đưa ra những giải pháp cho nó. Tuy nhiên đó chưa chắc đã là những phương án tối ưu nhất. Tốt hơn hết bạn nên áp dụng những Patterns để đem lại những phương án tối ưu và chất lượng.
  • Design Patterns giúp bạn tái sử dụng code và dễ dàng mở rộng code.
  • Design Patterns cung cấp giải pháp ở dạng tổng quát, giúp tăng tốc độ phát triển phần mềm bằng cách đưa ra các mô hình test, mô hình phát triển đã qua kiểm nghiệm.
  • Khi bảo trì những dự án nếu được code theo các pattern thì chắc hẳn việc đọc code và fix bug sẽ dễ dàng hơn cho lập trình viên cả về mặt chất lượng công việc cũng như thời gian.

3. Phân loại Design Pattern

  • Hiện nay, có 26 design pattern. 26 pattern này có thể được chia thành 3 nhóm chính: Khởi tạo (Creational Pattern), Cấu trúc (Structural Pattern) và Hành vi (Behavioral Pattern).

  • Mẫu thiết kế khởi tạo (Creational Patterns)

      Mẫu thiết kế này được dùng để tạo ra đối tượng cho một class (lớp) thích hợp. Class này sẽ là giải pháp cho vấn đề. Chúng đặc biệt hữu ích khi bạn đang tận dụng tính đa hình và cần phải lựa chọn giữa các class khác nhau trong runtime (thời gian chạy) thay vì compile time (thời gian biên dịch).

      Mẫu thiết kế khởi tạo hỗ trợ việc tạo ra các đối tượng trong một hệ thống mà không cần nhận dạng loại lớp cụ thể trong code. Vì vậy, bạn không cần phải viết những dòng code lớn, phức tạp để tạo bản sao của một đối tượng. Tuy nhiên, số đối tượng được tạo ra sẽ bị hạn chế. 

      Mẫu thiết kế cấu trúc (Structrure Patterns)

      Mẫu thiết kế cấu trúc tạo ra những cấu trúc lớn hơn từ những phần riêng lẻ, thường là của những class khác nhau. Mẫu này rất đa dạng, tùy thuộc vào dạng cấu trúc nào cần được tạo ra, với mục đích gì. Mẫu thiết kế cấu trúc quan tâm đến cách thức các class và đối tượng được cấu trúc để tạo nên những cấu trúc lớn hơn. Chúng sử dụng tính kế thừa để xây dựng các interface (giao diện) hay implementation (triển khai).

      Mẫu thiết kế hành vi (Behaviors Patterns)

      Mẫu thiết kế hành vi mô tả tương tác giữa các đối tượng và tập trung vào cách chúng giao tiếp với nhau. Chúng có thể giảm bớt những lưu đồ phức tạp thành liên kết giữa những đối tượng thuộc các class khác nhau. Mẫu hành vi còn được sử dụng để tạo thuật toán cho một class sử dụng. Nói đơn giản, chúng là một thông số có thể điều chỉnh được trong runtime. 

      Mẫu thiết kế này quan tâm đến các thuật toán và việc phân trách nhiệm giữa các đối tượng. Chúng không chỉ mô tả mẫu các đối tượng hay các class mà còn cả mẫu sự giao tiếp giữa chúng. Chúng khiến chúng ta quan tâm đến cách các đối tượng liên kết với nhau thay vì dòng kiểm soát. Mẫu thiết kế hành vi sử dụng tính kế thừa để phân phối hành vi giữa các class. 


    Creational Patterns

    • Abstract Factory
    • Builder
    • Factory
    • Prototype
    • Singleton




    Abstract Factory (Nhà máy trừu tượng): Abstract Factory là một mẫu thiết kế cho phép bạn tạo ra các họ đối tượng liên quan mà không cần chỉ định cụ thể lớp cụ thể của đối tượng. Nó cung cấp một giao diện trừu tượng để tạo ra các hình thức đối tượng, và một cách để chọn một nhóm đối tượng cụ thể từ trong họ đối tượng đó.


    Giả sử chúng ta có một nhà máy sản xuất thời trang với nhiều phân xưởng. Cái thì phụ trách sản xuất giày, cái sản xuất váy, cái thì sản xuất mũ,..Yêu cầu đặt ra là chúng ta cần tạo ra nhà máy sao cho nó đáp ứng được hầu hết các thay đổi thị hiếu người dùng và thị trường, như mùa hè thì sản xuất mẫu A, mùa đông lại sản xuất mẫu B cho từng dòng sản phẩm giày, váy, quần áo...Với điều kiện là không phải đầu tư thêm máy móc và nhân công hay sắp xếp lại bộ máy nhân sự vì rất tốn kém và mất nhiều thời gian. Điều này cũng tượng tự như việc thiết kế hệ thống phần mềm, ở bài viết này tôi giả sử phần mềm này là một nhà máy sản xuất thời trang như vậy. Với yêu cầu đặt ra như vậy chúng ta cần một cách để tạo ra các dòng sản phẩm một cách riêng biệt. Ví dụ mùa hè thì có giày mùa hè, váy mùa hè và mua đông lại có giày và váy của mùa đông.

        Ví dụ bằng javascript cho abstract factory 

    • // Abstract Factory
      class FashionFactory {
      createFootwear() {}
      createClothing() {}
      }

      // Concrete Factories
      class SummerFashionFactory extends FashionFactory {
      createFootwear() {
      return new SummerShoes();
      }
      createClothing() {
      return new SummerDress();
      }
      }

      class WinterFashionFactory extends FashionFactory {
      createFootwear() {
      return new WinterBoots();
      }
      createClothing() {
      return new WinterCoat();
      }
      }

      // Abstract Products
      class Footwear {
      constructor(name) {
      this.name = name;
      }
      display() {}
      }

      class Clothing {
      constructor(name) {
      this.name = name;
      }
      display() {}
      }

      // Concrete Products
      class SummerShoes extends Footwear {
      display() {
      console.log(`Summer shoes: ${this.name}`);
      }
      }

      class SummerDress extends Clothing {
      display() {
      console.log(`Summer dress: ${this.name}`);
      }
      }

      class WinterBoots extends Footwear {
      display() {
      console.log(`Winter boots: ${this.name}`);
      }
      }

      class WinterCoat extends Clothing {
      display() {
      console.log(`Winter coat: ${this.name}`);
      }
      }

      // Client Code
      function createFashionProducts(factory) {
      const footwear = factory.createFootwear();
      const clothing = factory.createClothing();
      return { footwear, clothing };
      }

      const summerFactory = new SummerFashionFactory();
      const winterFactory = new WinterFashionFactory();

      const summerProducts = createFashionProducts(summerFactory);
      summerProducts.footwear.display(); // Summer shoes
      summerProducts.clothing.display(); // Summer dress

      const winterProducts = createFashionProducts(winterFactory);
      winterProducts.footwear.display(); // Winter boots
      winterProducts.clothing.display(); // Winter coat



    Builder (Xây dựng): cho phép bạn chia quá trình xây dựng một đối tượng phức tạp thành các bước nhỏ. Bằng việc sử dụng DP này, chỉ với một đoạn code xây dựng duy nhất, bạn có thể tạo đối tượng thuộc nhiều hình thức khác nhau.



    Ví dụ: Hãy nghĩ về cách tạo đối tượng Nhà. Để xây một ngôi nhà đơn giản, bạn cần xây dựng bốn bức tường và một tầng, lắp cửa ra vào, lắp thêm cặp cửa sổ và dựng mái. 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), thì lúc đó bạn sẽ làm thế nào?

    Ví dụ về việc sử dụng Builder Pattern để xây dựng một đối tượng Nhà
    // Mẫu thiết kế Builder (Builder Pattern)
    class BuilderNha {
    constructor() {
    this.nha = new Nha();
    }

    xayTuong() {}
    xayTang() {}
    lapCua() {}
    lapCuaSo() {}
    dungMai() {}
    lapTienIch() {}
    }

    class QuanLyNha {
    constructor(builder) {
    this.builder = builder;
    }

    xayNha() {
    this.builder.xayTuong();
    this.builder.xayTang();
    this.builder.lapCua();
    this.builder.lapCuaSo();
    this.builder.dungMai();
    this.builder.lapTienIch();
    return this.builder.nha;
    }
    }

    class Nha {
    constructor() {
    this.cacPhan = [];
    }

    themPhan(phan) {
    this.cacPhan.push(phan);
    }

    hienThi() {
    console.log("Xây dựng Ngôi nhà với các phần:");
    this.cacPhan.forEach((phan) => {
    console.log(phan);
    });
    }
    }

    // Các Builder Cụ thể
    class NhaDonGianBuilder extends BuilderNha {
    xayTuong() {
    this.nha.themPhan("Tường đơn giản");
    }

    xayTang() {
    this.nha.themPhan("Tầng đơn giản");
    }

    lapCua() {
    this.nha.themPhan("Cửa đơn giản");
    }

    lapCuaSo() {
    this.nha.themPhan("Cửa sổ đơn giản");
    }

    dungMai() {
    this.nha.themPhan("Mái đơn giản");
    }
    }

    class NhaPhucTapBuilder extends BuilderNha {
    xayTuong() {
    this.nha.themPhan("Tường phức tạp");
    }

    xayTang() {
    this.nha.themPhan("Tầng phức tạp");
    }

    lapCua() {
    this.nha.themPhan("Cửa phức tạp");
    }

    lapCuaSo() {
    this.nha.themPhan("Cửa sổ phức tạp");
    }

    dungMai() {
    this.nha.themPhan("Mái phức tạp");
    }

    lapTienIch() {
    this.nha.themPhan("Tiện ích phức tạp (Hệ thống sưởi, ống nước, dây điện)");
    }
    }

    // Mã Khách hàng
    const builderNhaDonGian = new NhaDonGianBuilder();
    const builderNhaPhucTap = new NhaPhucTapBuilder();

    const quanLyNha1 = new QuanLyNha(builderNhaDonGian);
    const nhaDonGian = quanLyNha1.xayNha();
    nhaDonGian.hienThi();

    const quanLyNha2 = new QuanLyNha(builderNhaPhucTap);
    const nhaPhucTap = quanLyNha2.xayNha();
    nhaPhucTap.hienThi();




    Factory Pattern (Mẫu Nhà máy): Factory Pattern là một trong những Pattern phổ biến trong lập trình hướng đối tượng. Nhiệm vụ của Factory Pattern là quản lý và trả về các đối tượng theo yêu cầu, giúp cho việc khởi tạo đổi tượng một cách linh hoạt hơn.

    Giả sử bạn muốn mua một chiếc xe ô tô, bạn sẽ phải đến các cửa hàng để xem xét các xe trước khi mua. Các cửa hàng sẽ đưa xe ra cho bạn xem.

    Việc làm này có vẻ mất thời gian và công sức của bạn đến từng cửa hàng để xem. Tuy nhiên có một cách khác đơn giản hơn, đó là đến một đại lý ô tô có bán nhiều hãng để xem xét hết tất cả các xe. 

    1. // Abstract Factory
      class DaiLyOto {
      taoXeOto(hang) {}
      }

      // Concrete Factories
      class HondaDaiLyOto extends DaiLyOto {
      taoXeOto() {
      return new HondaXeOto();
      }
      }

      class ToyotaDaiLyOto extends DaiLyOto {
      taoXeOto() {
      return new ToyotaXeOto();
      }
      }

      class FordDaiLyOto extends DaiLyOto {
      taoXeOto() {
      return new FordXeOto();
      }
      }

      // Abstract Product
      class XeOto {
      constructor() {
      this.thongTin = "Xe ô tô";
      }
      hienThiThongTin() {}
      }

      // Concrete Products
      class HondaXeOto extends XeOto {
      hienThiThongTin() {
      console.log("Đây là một chiếc xe Honda.");
      }
      }

      class ToyotaXeOto extends XeOto {
      hienThiThongTin() {
      console.log("Đây là một chiếc xe Toyota.");
      }
      }

      class FordXeOto extends XeOto {
      hienThiThongTin() {
      console.log("Đây là một chiếc xe Ford.");
      }
      }

      // Client Code
      function muaXeOto(daiLy, hang) {
      const xeOto = daiLy.taoXeOto(hang);
      xeOto.hienThiThongTin();
      }

      const daiLyHonda = new HondaDaiLyOto();
      const daiLyToyota = new ToyotaDaiLyOto();
      const daiLyFord = new FordDaiLyOto();

      muaXeOto(daiLyHonda, "Honda"); // Đây là một chiếc xe Honda.
      muaXeOto(daiLyToyota, "Toyota"); // Đây là một chiếc xe Toyota.
      muaXeOto(daiLyFord, "Ford"); // Đây là một chiếc xe Ford.
    Prototype Pattern (Mẫu Prototype): Prototype Pattern cho phép bạn tạo ra các đối tượng mới bằng cách sao chép một đối tượng hiện có. 






    Dưới đây là một ví dụ về cách sử dụng mẫu thiết kế Prototype trong JavaScript để triển khai các lớp cho phần vẽ hình (Shape), bao gồm hình tròn (Circle), vuông (Square) và tam giác (Triangle):

    Abstract class của 3 thằng này là Shape. Tất nhiên, tròn hay vuông thì đều là một cái hình mà thôi

    // Abstract Prototype
    class Shape {
    clone() {}
    draw() {}
    }

    // Concrete Prototypes
    class Circle extends Shape {
    constructor(radius) {
    super();
    this.radius = radius;
    }

    clone() {
    return new Circle(this.radius);
    }

    draw() {
    console.log(`Vẽ hình tròn bán kính ${this.radius}`);
    }
    }

    class Square extends Shape {
    constructor(sideLength) {
    super();
    this.sideLength = sideLength;
    }

    clone() {
    return new Square(this.sideLength);
    }

    draw() {
    console.log(`Vẽ hình vuông cạnh ${this.sideLength}`);
    }
    }

    class Triangle extends Shape {
    constructor(side1, side2, side3) {
    super();
    this.side1 = side1;
    this.side2 = side2;
    this.side3 = side3;
    }

    clone() {
    return new Triangle(this.side1, this.side2, this.side3);
    }

    draw() {
    console.log(`Vẽ hình tam giác với các cạnh: ${this.side1}, ${this.side2}, ${this.side3}`);
    }
    }

    // Client Code
    const originalCircle = new Circle(5);
    const clonedCircle = originalCircle.clone();
    clonedCircle.draw(); // Vẽ hình tròn bán kính 5

    const originalSquare = new Square(4);
    const clonedSquare = originalSquare.clone();
    clonedSquare.draw(); // Vẽ hình vuông cạnh 4

    const originalTriangle = new Triangle(3, 4, 5);
    const clonedTriangle = originalTriangle.clone();
    clonedTriangle.draw(); // Vẽ hình tam giác với các cạnh: 3, 4, 5



    Singleton Pattern (Mẫu Singleton): Singleton Pattern đảm bảo rằng chỉ có một thể hiện duy nhất của một lớp trong hệ thống. 


    • Đây là design pattern được sử dụng khá phổ biến. Rất nhiều framwork sử dụng design pattern này. Pattern này được sử dụng khi ta muốn tạo một object từ một class và muốn chắc chắn rằng chỉ có một object được tạo từ nó. Implement cho design pattern này
    Dưới đây là một ví dụ về cách triển khai mẫu thiết kế Singleton trong JavaScript để quản lý trạng thái nhạc nền (mở hoặc tắt):

    class MusicManager {
    constructor() {
    this.musicPlaying = false;
    }

    static getInstance() {
    if (!MusicManager.instance) {
    MusicManager.instance = new MusicManager();
    }
    return MusicManager.instance;
    }

    playMusic() {
    if (!this.musicPlaying) {
    console.log("Bật nhạc nền.");
    this.musicPlaying = true;
    } else {
    console.log("Nhạc đã đang chạy.");
    }
    }

    stopMusic() {
    if (this.musicPlaying) {
    console.log("Tắt nhạc nền.");
    this.musicPlaying = false;
    } else {
    console.log("Nhạc đã tắt.");
    }
    }
    }

    // Sử dụng Singleton
    const musicManager1 = MusicManager.getInstance();
    const musicManager2 = MusicManager.getInstance();

    musicManager1.playMusic(); // Bật nhạc nền.
    musicManager2.playMusic(); // Nhạc đã đang chạy.

    musicManager1.stopMusic(); // Tắt nhạc nền.
    musicManager2.stopMusic(); // Nhạc đã tắt.







    Đăng nhận xét

    Mới hơn Cũ hơn