![](https://i.imgur.com/FZxrH0V.png) # Design Patterns - Javascript --- ## Strategy Pattern ***`--- Mọi con đường đều dẫn đến thành Rome ---`*** Vd về Khi di chuyển đến thành Rome: - Nếu ở VN thì phải đi máy bay đến Italia --> sau đó đi đến Rome - Cỏn ở Italia rồi chúng ta có thể di chuyển bằng xe hơi - Còn nếu ở gần Rome rồi ta có thể đi bộ vào thành Rome... **Kết luận:** Dẫu cho đi bằng phương tiện gì, con đường nào thì điểm cuối cùng vẫn là đến thành Rome >Strategy chỉ là 1 mô hình || bài toán sẽ được xử lý hoặc giải quyết bằng nhiều cách khác nhau (mọi cách thức đều ra được kết quả mong muốn) ![](https://i.imgur.com/vUdxurm.png) ```javascript // Bài toán tính Promotion của Shopee // ----------------------------------- // Sẽ có nhiều chiến lược (strategy) dùng để giải quyết nhiều Promotion khác nhau function defaultPromotion(originPrice) { return originPrice * 0.9; } function blackFriday(originPrice) { return originPrice * 0.8; } function hotDeal(originPrice) { let random = Math.floor(Math.random() * 5) / 10 || 0.1; return originPrice * (1 - random); } const promotions = { default: defaultPromotion, blackFriday, hotDeal, }; // Hàm tính toán các chiến lược (strategy) Promotion riêng biệt theo từng type nhận vào function getPricePromotionShopee(originalPrice, type = 'default') { return promotions[type](originalPrice); } console.log(getPricePromotionShopee(15000, 'blackFriday')); // 120000 ``` --- --- --- ## Singleton Pattern Chúng ta có thể hiểu **Singleton Pattern** sẽ đảm bảo rằng chỉ có **duy nhất một instance** (đối tượng) của một class được tạo ra **trong suốt quá trình chạy chương trình**, và các truy cập đến đối tượng này đều thông qua một phương thức cung cấp bởi class đó. >*Việc sử dụng Singleton Pattern sẽ giúp bạn tiết kiệm bộ nhớ và tăng hiệu suất trong các trường hợp cần sử dụng một đối tượng duy nhất trong suốt vòng đời của ứng dụng* ### *Vd dễ hiểu như sau:* Một ngày nọ CR7 bị hư xe ôtô, sáng hôm sau anh ấy đến tìm mình và hỏi mượn xe Toyota Vios của mình (xe dư ko dùng) trong ít hôm để đi lại, vì là bạn bè thân thiết nên mình cho CR7 mượn. Bỗng đến chiều tối thì Messi có liên hệ mình để hỏi mượn chiếc Toyota Vios đấy vì vợ anh ấy đang đi xe của anh để dự tiệc và anh có chuyện gấp cần chạy về nhà bố mẹ....... Nhưng rất tiếc mình không thể cho Messi mượn được vì chiếc Toyota Vios ấy mình đã cho CR7 mượn lúc sáng mất rồi.... ```typescript class Database { private static instance: Database; private constructor() { // Khởi tạo đối tượng database } public static getInstance(): Database { if (!Database.instance) { Database.instance = new Database(); } return Database.instance; } // Các phương thức khác của đối tượng database public connect() { // Kết nối tới cơ sở dữ liệu } public disconnect() { // Ngắt kết nối tới cơ sở dữ liệu } } // Sử dụng đối tượng Database const db1 = Database.getInstance(); const db2 = Database.getInstance(); console.log(db1 === db2); // true db1.connect(); db1.disconnect(); ``` Ở ví dụ trên thì class **Database** chỉ có một **`instance`** được tạo ra và trả về qua phương thức **`getInstance()`** Nếu **`getInstance()`** được gọi lần đầu ---> một **`instance`** mới sẽ được tạo ra, còn nếu đã được gọi trước đó thì **`instance`** đã được tạo sẽ trả về Các phương thức trong class **Database** có thể được truy cập thông qua **`instance`** đã trả về... --- --- --- ## Observer Pattern Khi **`đối tượng chủ thể`** có bất kì một sự thay đổi nào thì các **`đối tượng quan sát`** đều sẽ nhận được tín hiệu thay đổi ấy Observer Pattern được áp dụng rộng rãi ở nhiều trường hợp trong cuộc sống như: * Mô hình đèn xanh đèn đỏ * Đăng ký (subscribe) kênh youtube và nhấn chuông nhận thông báo * Chiến dịch quảng cáo Iphone 14 (khi user click pre-order và điền thông tin mail, tên, sdt... --> khi IP14 có hàng hay xác nhận mua bán thì thông tin sẽ dc gửi về mail) * Game LMHT... (giải thích qua Code) Mô hình MVC có áp dụng Observer Pattern ### Mô hình đèn xanh đèn đỏ: 1. Đối tượng chủ thể là **`cột đèn giao thông`** 2. Đối tượng quan sát là **`người đi đường`** Khi có sự thay đổi tín hiệu đèn giao thông, vd đèn xanh thì những đối tượng quan sát là người đi đường sẽ di chuyển.... ### Đăng ký (subscribe) kênh youtube và nhấn chuông nhận thông báo: 1. Đối tượng chủ thể là **`Kênh Youtube đc subscribe`** 2. Đối tượng quan sát là **`người xem`** Khi kệnh Youtube có video mới thì người xem nếu đã dky kênh và nhấn chuông dky thì sẽ nhận dc thông báo. --- ```javascript // Áp dụng Observer Pattern vào game LMHT: khi 1 tướng nào đó bị tấn công --> Ping thông tin location đến các đồng đội khác trong team // Đối tượng quan sát class Observer { // nhận tên nhân vật constructor(name) { this.nameCharacter = name; } updateStatus(location) { // Khi bị tấn công --> goToHelp() this.goToHelp(location); } // gọi cứu trợ --> Ping thông tin về Location goToHelp(location) { console.log(`:::PING::: --- ${JSON.stringify(location)} --- :::PING:::`); console.log("Let's go...") } } class Subject { constructor() { this.observerList = []; } // Thêm observer (thêm 1 chủ thể quan sát) addObserver(observer) { this.observerList.push(observer); } // Thông báo cho đồng đội khi bị tấn công --> notify() notify(location) { this.observerList.forEach((observer) => observer.updateStatus(location)); } } // ------- Usage ------- // Tạo 1 chủ thể const subject = new Subject(); // Tạo 1 tướng LOL const akali = new Observer('Akali'); const varus = new Observer('Varus'); // Thêm akali + varus vào trong team subject.addObserver(akali); subject.addObserver(varus); // push location đến team khi bị tấn công subject.notify({ lat: Math.random(100), lng: Math.random(100), }); ``` --- --- --- ## Facade Pattern Face có thể hiểu là gói gọn lại các công việc, hình thức triển khai phức tạp vào bên trong 1 mô hình 1. Khi bạn gọi điện thoại đến tổng đài để đặt hàng mua ABC thì đằng sau 1 cuộc gọi đặt hàng là cả 1 hệ thống gồm nhiều các công việc được diễn ra vd như: * Kiểm tra tồn kho, mẫu mã, size... sản phẩm * Ghi nhận đơn hàng * Tính toán thời gian giao hàng * Tính phí, thuế * Thanh toán đơn hàng * Ghi nhận doanh thu * ... ![](https://i.imgur.com/RiaGU60.png) ```javascript // Giả lập case thanh toán của Shopee gồm nhiều bước, vd: tính thuế, giảm giá, giao hàng.... class Discount { // Giảm 10% calc(value) { return value * 0.9; } } class Shipping { calc() { return 5000; } } class Fees { // Phí VAT 5% calc(value) { return value * 1.05; } } class ShopeeFacadePattern { constructor() { this.discount = new Discount(); this.shipping = new Shipping(); this.fees = new Fees(); } calc(value) { value = this.fees.calc(value); value = this.discount.calc(value); value += this.shipping.calc(); return value; } } const shopee = new ShopeeFacadePattern(); console.log(shopee.calc(150000)); ``` > Note: Facade Pattern khác với Strategy Pattern ở điểm như sau: > **Facade** là 1 mô hình **thể hiện đơn giản** **nhưng đằng sau là** tập một **các công việc phức tạp** > **Strategy** là 1 mô hình **chứa nhiều chiến lược** và **mỗi chiến lược** đều có thể **giải quyết 1 bài toán cụ thể** *(ko áp dụng tất cả chiến lược như Facade để giải quyết 1 bài toán chung)* --- --- --- ## Proxy Pattern Đây là mô hình, một mẫu thiết kế thể hiện rằng **giữa 2 chủ thế** sẽ có 1 **Proxy** đứng trung gian để nhận request và trả response **giữa 2 chủ thể** Proxy ở đây chỉ là mô hình trung gian, **được ủy quyền để nhận thông tin (không đc chỉnh sửa, thay đổi thông tin nhận...)** từ một bên (vd: client) và đưa cho bên còn lại (vd: server) xử lý --> sau đấy nhận kết quả và trả về cho bên gửi thông tin. ![](https://i.imgur.com/4MhYvYp.png) Vd trong đời sống cụ thể như sau: 1. Người tiêu dùng đi rút tiền gửi trong ngân hàng thông qua các cây ATM 2. Người bán đất nhờ môi giới rao bán, tìm khách hàng mua đất 3. Thư ký là người nhận các yêu cầu từ nhiều phòng ban, nhân viên --> sắp xếp lại đưa đến giám đốc xử lý ```javascript // Proxy trong việc về mua bán bất động sản // Có 2 chủ thể là Seller (người bán), Buyer (người mua), BrokerProxy (môi giới - người trung gian) class Seller { receiveRequest(content) { console.log(`OK OK OK::: ${content}`); return true; } } class Buyer { constructor() { this.brockerProxy = new BrockerProxy(); } sendRequest(content) { this.brockerProxy.receiveRequest(content); } } class BrockerProxy { constructor() { this.seller = new Seller(); } receiveRequest(content) { let isOk = this.seller.receiveRequest(content); if (!!isOk) { console.log('Complete'); } } } const buyer = new Buyer(); console.log(buyer.sendRequest('Buy the house with $150K')); ``` --- --- --- ## Factory Pattern Cho phép chúng ta **tạo các đối tượng** mà **không cần biết** chi tiết cách chúng **được tạo** ra sao. Chúng ta chỉ cần truyền một tham số vào một hàm hoặc phương thức để tạo ra đối tượng mong muốn. Trong nhà máy sẽ có khoảng 10 mẫu xe được chế tạo em chỉ cần đưa thông tin vd tên xe “A 001” là tự động nhà máy biết là xe đó mẫu mã ntn, động cơ gì, màu sắc ra sao bla bla để chế tạo —> đưa ra cho em > Factory Pattern thuộc nhóm Creational Pattern. ### Simple Factory Vd: ứng dụng vào vận chuyển Logistic cơ bản. Ta sẽ nhận thông tin về **`Khối lượng hàng hóa`** ---> Trả về **`Phương tiện xe vận chuyển phù hợp`** ```javascript class ServiceLogistic { constructor(name = 'Truck 001', price = '100.000 VNĐ', doors = 10) { this.name = name; this.price = price; this.doors = doors; } // func getTransport nhận vào cargoVolume (khối lượng hàng hóa) --> trả về loại phương tiện phù hợp static getTransport = (cargoVolume) => { switch (cargoVolume) { case '10': return new ServiceLogistic(); case '20': return new ServiceLogistic('Truck 002', '150.000 VNĐ', 16); } }; } console.log('Thông tin vận chuyển: ', ServiceLogistic.getTransport('10')); // ServiceLogistic{name: "Truck 001", price: "100.000 VNĐ", doors: 10} console.log('Thông tin vận chuyển: ', ServiceLogistic.getTransport('20')); // ServiceLogistic{name: "Truck 002", price: "150.000 VNĐ", doors: 16} ``` > Note: ở vd này có vấn đề khi thay đổi ### Factory Method Khi thực hiện Factory Method sẽ có thể mở rộng ra thêm nhiều class khác nhau và ko phạm phải tính đóng mở của function như **Simple Factory** > Như ở **Simple Factory** khi muốn mở rộng các instance thì phải đụng đến code logic tầng core để update hay mở rộng... ```javascript // Xây dựng mô hình Factory Method vào mô hình khách hàng chọn phương tiện vận chuyển, đi lại... class Car { constructor({ name = 'BMW 001', doors = 4, price = '1000 VND', customerInfo = {}, }) { this.name = name; this.doors = doors; this.price = price; this.customerInfo = customerInfo; } } // core create new transportation class ServiceTransportation { transporClass = Car; getTransport = (customerInfo) => { return new this.transporClass(customerInfo); }; } class Motobike { constructor({ name = 'SH 001', doors = 1, price = '250 VND', customerInfo = {}, }) { this.name = name; this.doors = doors; this.price = price; this.customerInfo = customerInfo; } } // order for customer by Car const serviceCar = new ServiceTransportation(); // default lúc này là class Car console.log( 'CAR:: ', serviceCar.getTransport({ customerInfo: { fullName: 'NTA 001', age: 24 } }) ); /** * { "name": "BMW 001", "doors": 4, "price": "1000 VND", "customerInfo": { "fullName": "NTA 001", "age": 24 } } */ // order for customer by Motobike const serviceMotobike = new ServiceTransportation(); serviceMotobike.transporClass = Motobike; console.log( 'MOTOBIKE:: ', serviceMotobike.getTransport({ customerInfo: { fullName: 'NTA 002', age: 24 }, }) ); /** * { "name": "SH 001", "doors": 1, "price": "250 VND", "customerInfo": { "fullName": "NTA 002", "age": 24 } } */ ``` ### Abstract Factory Mô hình này giúp tách biệt việc tạo đối tượng ra khỏi việc sử dụng đối tượng, giúp mã của bạn trở nên dễ bảo trì hơn và giảm thiểu sự phụ thuộc giữa các lớp. ![](https://i.imgur.com/zCS2Zrq.png) > *Mô hình Abstract Factory cơ bản* Mỗi 1 mẫu xe sẽ dc 1 nhà máy sản xuất, vd **LuxA20** dc nhà máy **`LuxA20Factory`** sản xuất còn **LuxSA20** dc nhà máy **`LuxSA20Factory`** sản xuất. Nhưng phần khung, gầm, vô lăng, ghế... và rất nhiều phần khác của chiếc xe có thể dc **sản xuất chung** bởi các nhà máy trong công ty **`abcxyz`** nào đấy Từ đấy **Abstract Factory** có thể hiểu là 1 nhà máy to bự nhất, là nhà máy quản lý nhiều nhà máy nhỏ bên trong... **Abstract Factory** như là 1 nhà máy lớn chứa ```typescript abstract class VIN { abstract run(): void; } class LUXA20 extends VIN { run(): void { console.log('LUXA20 Ô tô'); } } class LUXSA20 extends VIN { run(): void { console.log('LUXSA20 Ô tô'); } } abstract class VINFactory { abstract produceLUXA20(): LUXA20; abstract produceLUXSA20(): LUXSA20; } class ConcreteVINFactory extends VINFactory { produceLUXA20(): LUXA20 { return new LUXA20(); } produceLUXSA20(): LUXSA20 { return new LUXSA20(); } } const vinFactory = new ConcreteVINFactory(); const luxA20 = vinFactory.produceLUXA20(); const luxSA20 = vinFactory.produceLUXSA20(); luxA20.run(); // LUXA20 Ô tô luxSA20.run(); // LUXSA20 Ô tô ``` Giải thích đoạn code trên: - Lớp abstract VIN với một phương thức abstract run(). Lớp này là lớp cơ sở cho các lớp khác, có chức năng chung cho các lớp con của nó. - Hai lớp kế thừa từ VIN là LUXA20 và LUXSA20, mỗi lớp đều triển khai phương thức run() của nó để in ra một thông điệp riêng. - Lớp abstract VINFactory định nghĩa một bản thiết kế cho factory tạo ra các đối tượng của các lớp con của VIN. - Lớp ConcreteVINFactory triển khai VINFactory bằng cách định nghĩa phương thức produceLUXA20() và produceLUXSA20() để trả về các đối tượng của LUXA20 và LUXSA20 tương ứng. - Cuối cùng, trong hàm main, một đối tượng của ConcreteVINFactory được tạo ra và sử dụng để tạo ra hai đối tượng luxA20 và luxSA20 bằng cách gọi hai phương thức produceLUXA20() và produceLUXSA20(). Sau đó, phương thức run() được gọi trên cả hai đối tượng để in ra thông điệp tương ứng với mỗi lớp. >Ví dụ thêm, nếu bạn đang viết một ứng dụng quản lý động vật, bạn có thể định nghĩa một **abstract class** là **Animal** và định nghĩa một **abstract method** là **`makeSound()`**. Tất cả các lớp con của Animal đều phải triển khai phương thức **`makeSound()`**. > >Nhưng các lớp con khác nhau có thể triển khai khác nhau cho phương thức này. Ví dụ, lớp con **Dog** có thể triển khai phương thức makeSound để sủa, trong khi lớp con **Cat** có thể triển khai phương thức makeSound để kêu meo meo. > >Điều này giúp cho việc quản lý các lớp con của Animal trở nên dễ dàng và giảm thiểu sự trùng lặp trong mã.