Hướng dẫn design pattern javascript - mẫu thiết kế javascript
Xin chào các bạn, Show 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 patternII. Phân loại Design Patterns
2.2 Structural Design Patterns 2.3 Behavioral Design Patterns III. Constructor Pattern II. Phân loại Design Patterns2.1 Creational Design Patterns2.2 Structural Design Patterns 2.3 Behavioral Design PatternsConstruct pattern, factory pattern, prototype pattern và singleton pattern. 2.2 Structural Design Patterns2.3 Behavioral Design Patterns III. Constructor PatternAdapter Pattern, Composite Pattern, Decorator Pattern, Façade Pattern, Flyweight Pattern, and Proxy Pattern. 2.3 Behavioral Design PatternsIII. Constructor PatternChain of Responsibility Pattern, Command Pattern, Iterator Pattern, Mediator Pattern, Observer Pattern, State Pattern, Strategy Pattern, và Template Pattern. III. Constructor PatternVI. 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 PatternV. 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); // true3, 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); // true4 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); // true5. 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); // true6. 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 PatternSingleton 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); // true7 – 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); // true8 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); // true7 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); // true7 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()); // mongo2, 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); // true7 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); // true8. 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()); // mongo5 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()); // mongo6 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()); // mongo7 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()); // mongo5 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()); // mongo9. // 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ếtOk 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. |