Hướng dẫn design pattern javascript - mẫu thiết kế javascript

Xin chào các bạn,

Trong bài viết này, mình sẽ chỉ cho các bạn một số design pattern được sử dụng trong javascript, để giúp các bạn viết code tốt hơn, dễ đọc hơn, dễ bảo trì hơn. Để đọc và hiểu bài viết này, thì các bạn cần phải có sẵn kiến thức về javascript nhé, tuy nhiên không cần nhiều đâu, chỉ cần hiểu được một số khái niệm như class, object là được.design pattern được sử dụng trong javascript, để giúp các bạn viết code tốt hơn, dễ đọc hơn, dễ bảo trì hơn. Để đọc và hiểu bài viết này, thì các bạn cần phải có sẵn kiến thức về javascript nhé, tuy nhiên không cần nhiều đâu, chỉ cần hiểu được một số khái niệm như class, object là được.

Trong phần 1 này, mình sẽ trình bày tới các bạn về tổng quan design pattern, phân loại design pattern và một số mẫu design pattern đầu tiên.

  • I. Giới thiệu về design pattern
  • II. Phân loại Design Patterns
    • 2.1 Creational Design Patterns
    • 2.2 Structural Design Patterns
    • 2.3 Behavioral Design Patterns
  • III. Constructor Pattern
  • VI. Factory Pattern
  • V. Prototype Pattern
  • VI. Singleton Pattern
  • VII. Adapter Pattern
  • VIII. Tạm kết

I. Giới thiệu về design pattern

II. Phân loại Design Patterns

2.1 Creational Design Patternspattern là một thuật ngữ được sử dụng trong ngành kỹ nghệ phần mềm nói chung, là giải pháp tái sử dụng cho việc giải quyết các vấn đề giống nhau và thường xuyên ra trong quá trình phát triển phần mềm

2.2 Structural Design Patterns

2.3 Behavioral Design Patterns

III. Constructor Pattern

II. Phân loại Design Patterns

2.1 Creational Design Patterns

2.2 Structural Design Patterns

2.3 Behavioral Design PatternsConstruct pattern, factory pattern, prototype patternsingleton pattern.

2.2 Structural Design Patterns

2.3 Behavioral Design Patterns

III. Constructor PatternAdapter Pattern, Composite Pattern, Decorator Pattern, Façade Pattern, Flyweight Pattern, and Proxy Pattern.

2.3 Behavioral Design Patterns

III. Constructor PatternChain of Responsibility Pattern, Command Pattern, Iterator Pattern, Mediator Pattern, Observer Pattern, State Pattern, Strategy Pattern, và Template Pattern.


III. Constructor Pattern

VI. Factory Pattern

V. Prototype Pattern

// Đây là cách khởi tạo truyền thống của JS
function Hero(name, specialAbility) {
  // Gán các thuộc tính cho đối tượng
  this.name = name;
  this.specialAbility = specialAbility;

  // Khai báo phương thức cho đối tượng
  this.getDetails = function() {
    return this.name + ' can ' + this.specialAbility;
  };
}

// Đây là khi sử dụng cú pháp ES6
class Hero {
  constructor(name, specialAbility) {
    // Gán các thuộc tính cho đối tượng
    this._name = name;
    this._specialAbility = specialAbility;

    // Khai báo phương thức cho đối tượng
    this.getDetails = function() {
      return `${this._name} can ${this._specialAbility}`;
    };
  }
}

// Tạo một đối tượng mới từ lớp Hero ở trên
const iRonMan = new Hero('Iron Man', 'fly');

console.log(iRonMan.getDetails()); // Iron Man can fly

VI. Singleton Pattern

VI. Factory Pattern

V. Prototype Pattern

VI. Singleton Pattern

class BallFactory {
  constructor() {
    this.createBall = function(type) {
      let ball;
      if (type === 'football' || type === 'soccer') ball = new Football();
      else if (type === 'basketball') ball = new Basketball();
      ball.roll = function() {
        return `The ${this._type} is rolling.`;
      };

      return ball;
    };
  }
}

class Football {
  constructor() {
    this._type = 'football';
    this.kick = function() {
      return 'You kicked the football.';
    };
  }
}

class Basketball {
  constructor() {
    this._type = 'basketball';
    this.bounce = function() {
      return 'You bounced the basketball.';
    };
  }
}

// creating objects
const factory = new BallFactory();

const myFootball = factory.createBall('football');
const myBasketball = factory.createBall('basketball');

console.log(myFootball.roll()); // The football is rolling.
console.log(myBasketball.roll()); // The basketball is rolling.
console.log(myFootball.kick()); // You kicked the football.
console.log(myBasketball.bounce()); // You bounced the basketball.

V. Prototype Pattern

Đây cũng là một pattern thuộc nhóm Creational Pattern. Pattern này sẽ sử dụng một một đối tượng khác như một “khung xương” để khởi tạo nên một đối tượng mới.

Pattern này đặc biệt quan trọng với Javascript, bởi vì Javascript sử dụng Prototype rất nhiều. Chính vì thế pattern này sử dụng với Js mới thật sự phát huy được hết sức mạnh của Js.

Prototype trong Js là một chủ đề tốn giấy mực, nếu bạn chưa hiểu Prototype trong Js là gì thì cũng không sao. Bạn vẫn có thể xem tiếp ví dụ dưới đây để hiểu hơn về pattern này.

Trong ví dụ sau, mình có sẵn một object

const car = {
  noOfWheels: 4,
  start() {
    return 'started';
  },
  stop() {
    return 'stopped';
  },
};
// Sử dụng Object.create được khuyên dùng khi sử dụng với cú pháp ES5
// Object.create(proto[, propertiesObject])

const myCar = Object.create(car, { owner: { value: 'John' } });

console.log(myCar.__proto__ === car); // true
3, sau đó mình sẽ sử dụng object này kết hợp với hàm
const car = {
  noOfWheels: 4,
  start() {
    return 'started';
  },
  stop() {
    return 'stopped';
  },
};
// Sử dụng Object.create được khuyên dùng khi sử dụng với cú pháp ES5
// Object.create(proto[, propertiesObject])

const myCar = Object.create(car, { owner: { value: 'John' } });

console.log(myCar.__proto__ === car); // true
4 của Javascript để tạo ra một object mới có tên là
const car = {
  noOfWheels: 4,
  start() {
    return 'started';
  },
  stop() {
    return 'stopped';
  },
};
// Sử dụng Object.create được khuyên dùng khi sử dụng với cú pháp ES5
// Object.create(proto[, propertiesObject])

const myCar = Object.create(car, { owner: { value: 'John' } });

console.log(myCar.__proto__ === car); // true
5. Object mới này sẽ bổ sung thêm một thuộc tính mới là
const car = {
  noOfWheels: 4,
  start() {
    return 'started';
  },
  stop() {
    return 'stopped';
  },
};
// Sử dụng Object.create được khuyên dùng khi sử dụng với cú pháp ES5
// Object.create(proto[, propertiesObject])

const myCar = Object.create(car, { owner: { value: 'John' } });

console.log(myCar.__proto__ === car); // true
6.

const car = {
  noOfWheels: 4,
  start() {
    return 'started';
  },
  stop() {
    return 'stopped';
  },
};
// Sử dụng Object.create được khuyên dùng khi sử dụng với cú pháp ES5
// Object.create(proto[, propertiesObject])

const myCar = Object.create(car, { owner: { value: 'John' } });

console.log(myCar.__proto__ === car); // true

VI. Singleton Pattern

Singleton pattern là một pattern đặc biệt trong nhóm creational pattern. Pattern này chỉ cho phép có duy nhất một đối tượng được khởi tạo từ class. Cách hoạt động của nó như sau – Nếu chưa có đối tượng nào được khởi tạo từ class trước đó, nó sẽ khởi tạo đối tượng từ class và trả về đối tượng đó, nhưng nếu đối tượng đã được khởi tạo rồi thì thay vì khởi tạo lần nữa, nó sẽ trả về đối tượng đã được khởi tạo trước đó.

Một ví dụ quen thuộc trong bài toán thực tế đó là Mongooso (một thư viện ODM Nodejs cho MongoDB), thư viện này tận dụng rất tốt và hiểu quả với Pattern này.

Trong ví dụ dưới đây, chúng ta có một class tên là

const car = {
  noOfWheels: 4,
  start() {
    return 'started';
  },
  stop() {
    return 'stopped';
  },
};
// Sử dụng Object.create được khuyên dùng khi sử dụng với cú pháp ES5
// Object.create(proto[, propertiesObject])

const myCar = Object.create(car, { owner: { value: 'John' } });

console.log(myCar.__proto__ === car); // true
7 – là một class singleton. Đầu tiên, chúng ta khởi tạo một đối tượng
const car = {
  noOfWheels: 4,
  start() {
    return 'started';
  },
  stop() {
    return 'stopped';
  },
};
// Sử dụng Object.create được khuyên dùng khi sử dụng với cú pháp ES5
// Object.create(proto[, propertiesObject])

const myCar = Object.create(car, { owner: { value: 'John' } });

console.log(myCar.__proto__ === car); // true
8 bằng việc sử dụng từ khóa
class BallFactory {
  constructor() {
    this.createBall = function(type) {
      let ball;
      if (type === 'football' || type === 'soccer') ball = new Football();
      else if (type === 'basketball') ball = new Basketball();
      ball.roll = function() {
        return `The ${this._type} is rolling.`;
      };

      return ball;
    };
  }
}

class Football {
  constructor() {
    this._type = 'football';
    this.kick = function() {
      return 'You kicked the football.';
    };
  }
}

class Basketball {
  constructor() {
    this._type = 'basketball';
    this.bounce = function() {
      return 'You bounced the basketball.';
    };
  }
}

// creating objects
const factory = new BallFactory();

const myFootball = factory.createBall('football');
const myBasketball = factory.createBall('basketball');

console.log(myFootball.roll()); // The football is rolling.
console.log(myBasketball.roll()); // The basketball is rolling.
console.log(myFootball.kick()); // You kicked the football.
console.log(myBasketball.bounce()); // You bounced the basketball.
0 gọi tới class
const car = {
  noOfWheels: 4,
  start() {
    return 'started';
  },
  stop() {
    return 'stopped';
  },
};
// Sử dụng Object.create được khuyên dùng khi sử dụng với cú pháp ES5
// Object.create(proto[, propertiesObject])

const myCar = Object.create(car, { owner: { value: 'John' } });

console.log(myCar.__proto__ === car); // true
7 như bình thường. Trong lần khởi tạo đầu tiên này, đối tượng sẽ được khởi tạo bởi vì trước đó chưa từng có đối tượng nào khác được khởi tạo từ lớp
const car = {
  noOfWheels: 4,
  start() {
    return 'started';
  },
  stop() {
    return 'stopped';
  },
};
// Sử dụng Object.create được khuyên dùng khi sử dụng với cú pháp ES5
// Object.create(proto[, propertiesObject])

const myCar = Object.create(car, { owner: { value: 'John' } });

console.log(myCar.__proto__ === car); // true
7 này. Tiếp theo, chúng ta tiếp tục khởi tạo một đối tượng mới tên là
class Database {
  constructor(data) {
    if (Database.exists) {
      return Database.instance;
    }
    this._data = data;
    Database.instance = this;
    Database.exists = true;
    return this;
  }

  getData() {
    return this._data;
  }

  setData(data) {
    this._data = data;
  }
}

// usage
const mongo = new Database('mongo');
console.log(mongo.getData()); // mongo

const mysql = new Database('mysql');
console.log(mysql.getData()); // mongo
2, trong lần khởi tạo này, class
const car = {
  noOfWheels: 4,
  start() {
    return 'started';
  },
  stop() {
    return 'stopped';
  },
};
// Sử dụng Object.create được khuyên dùng khi sử dụng với cú pháp ES5
// Object.create(proto[, propertiesObject])

const myCar = Object.create(car, { owner: { value: 'John' } });

console.log(myCar.__proto__ === car); // true
7 sẽ không khởi tạo thêm đối tượng mới nào nữa, thay vào đó nó sẽ tham chiếu tới đối tượng trước và trả về đối tượng đó, cụ thể trong ví dụ này sẽ trả về đối tượng
const car = {
  noOfWheels: 4,
  start() {
    return 'started';
  },
  stop() {
    return 'stopped';
  },
};
// Sử dụng Object.create được khuyên dùng khi sử dụng với cú pháp ES5
// Object.create(proto[, propertiesObject])

const myCar = Object.create(car, { owner: { value: 'John' } });

console.log(myCar.__proto__ === car); // true
8.

class Database {
  constructor(data) {
    if (Database.exists) {
      return Database.instance;
    }
    this._data = data;
    Database.instance = this;
    Database.exists = true;
    return this;
  }

  getData() {
    return this._data;
  }

  setData(data) {
    this._data = data;
  }
}

// usage
const mongo = new Database('mongo');
console.log(mongo.getData()); // mongo

const mysql = new Database('mysql');
console.log(mysql.getData()); // mongo

VII. Adapter Pattern

Đây là một dạng của structural pattern, được sử dụng khi mà bạn muốn thay thế một class cũ bằng một class mới. Pattern giúp các class có thể hoạt động cùng với nhau ngay cả khi chúng không thực sự tương thích với nhau.

Pattern này thường được sử dụng để tạo ra một lớp mới chứa các APIs mới “bao bọc” lại những APIs cũ. Vì chỉ đơn giản là “bọc” lại các APIs cũ nên các APIs cũ sẽ không bị thay đổi, và đương nhiên vẫn hoạt động bình thường ở những nơi khác trong hệ thống.

Pattern này được áp dụng khi mà chúng ta muốn mở rộng hoặc cải thiện một thành phần nào đó trong hệ thống mà không làm ảnh hưởng tới hệ thống hiện tại cho dù hệ thống hiện tại vẫn đang sử dụng các thành phần cũ đó.

Trong ví dụ dưới đây, chúng ta có một API cũ là class OldCalculator, và một API mới là class NewCalculator. Class OldCalculator có một phương thức là

class Database {
  constructor(data) {
    if (Database.exists) {
      return Database.instance;
    }
    this._data = data;
    Database.instance = this;
    Database.exists = true;
    return this;
  }

  getData() {
    return this._data;
  }

  setData(data) {
    this._data = data;
  }
}

// usage
const mongo = new Database('mongo');
console.log(mongo.getData()); // mongo

const mysql = new Database('mysql');
console.log(mysql.getData()); // mongo
5 thực hiện tính toán cho cả phép cộng và phép trừ. Trong khi class NewCalculator thì thực hiện phép cộng và phép trừ ở hai phương thức khác nhau. Một class thứ ba là
class Database {
  constructor(data) {
    if (Database.exists) {
      return Database.instance;
    }
    this._data = data;
    Database.instance = this;
    Database.exists = true;
    return this;
  }

  getData() {
    return this._data;
  }

  setData(data) {
    this._data = data;
  }
}

// usage
const mongo = new Database('mongo');
console.log(mongo.getData()); // mongo

const mysql = new Database('mysql');
console.log(mysql.getData()); // mongo
6 sẽ “bọc” class
class Database {
  constructor(data) {
    if (Database.exists) {
      return Database.instance;
    }
    this._data = data;
    Database.instance = this;
    Database.exists = true;
    return this;
  }

  getData() {
    return this._data;
  }

  setData(data) {
    this._data = data;
  }
}

// usage
const mongo = new Database('mongo');
console.log(mongo.getData()); // mongo

const mysql = new Database('mysql');
console.log(mysql.getData()); // mongo
7 trong khi vẫn giữ nguyên cấu trúc hàm
class Database {
  constructor(data) {
    if (Database.exists) {
      return Database.instance;
    }
    this._data = data;
    Database.instance = this;
    Database.exists = true;
    return this;
  }

  getData() {
    return this._data;
  }

  setData(data) {
    this._data = data;
  }
}

// usage
const mongo = new Database('mongo');
console.log(mongo.getData()); // mongo

const mysql = new Database('mysql');
console.log(mysql.getData()); // mongo
5 của
class Database {
  constructor(data) {
    if (Database.exists) {
      return Database.instance;
    }
    this._data = data;
    Database.instance = this;
    Database.exists = true;
    return this;
  }

  getData() {
    return this._data;
  }

  setData(data) {
    this._data = data;
  }
}

// usage
const mongo = new Database('mongo');
console.log(mongo.getData()); // mongo

const mysql = new Database('mysql');
console.log(mysql.getData()); // mongo
9.

// old interface
class OldCalculator {
  constructor() {
    this.operations = function(term1, term2, operation) {
      switch (operation) {
        case 'add':
          return term1 + term2;
        case 'sub':
          return term1 - term2;
        default:
          return NaN;
      }
    };
  }
}

// new interface
class NewCalculator {
  constructor() {
    this.add = function(term1, term2) {
      return term1 + term2;
    };
    this.sub = function(term1, term2) {
      return term1 - term2;
    };
  }
}

// Adapter Class
class CalcAdapter {
  constructor() {
    const newCalc = new NewCalculator();

    this.operations = function(term1, term2, operation) {
      switch (operation) {
        case 'add':
          // using the new implementation under the hood
          return newCalc.add(term1, term2);
        case 'sub':
          return newCalc.sub(term1, term2);
        default:
          return NaN;
      }
    };
  }
}

// usage
const oldCalc = new OldCalculator();
console.log(oldCalc.operations(10, 5, 'add')); // 15

const newCalc = new NewCalculator();
console.log(newCalc.add(10, 5)); // 15

const adaptedCalc = new CalcAdapter();
console.log(adaptedCalc.operations(10, 5, 'add')); // 15;

VIII. Tạm kết

Ok phần một đến đây là hết. Ở phần một này, các bạn hãy hiểu cho mình tầm quan trọng của design pattern, và thử hình dung lại trong quá trình các bạn làm việc các bạn đã vô tình gặp được pattern nào mà mình giới thiệu ở trên chưa nhé. Mình nghĩ chắc chắn là rồi, nếu bạn không thấy thì có thể là do bạn tạm thời chưa nhận ra thôi. Vì các pattern trên đều là những pattern rất hay được sử dụng trong quá trình làm việc.

Ở phần 2, mình sẽ giới thiệu tiếp đến các bạn một số pattern khác nữa, cũng rất thường xuyên được sử dụng trong dự án.