TypeScript Advanced – Phần 3 – Design Patterns – Structural Patterns

🎯 Mục tiêu

  • Giúp tổ chức cấu trúc đối tượng và class trong hệ thống phức tạp.
  • Giữ cho cấu trúc linh hoạt nhưng hiệu quả và dễ bảo trì.
  • Khác với creational patterns (tạo đối tượng), structural patterns giúp kết nối và sắp xếp chúng hợp lý hơn.

🧠 Khi nào nên dùng Structural Patterns

Trường hợpVai trò của Structural Pattern
🧩 Kết hợp nhiều đối tượng thành hệ thống lớnComposite, Facade, Bridge…
🔁 Thêm tính năng mà không sửa đổi code cũDecorator, Proxy
🔗 Kết nối các interface không tương thíchAdapter
🔍 Giảm phụ thuộc giữa các thành phầnTăng tính mở rộng, dễ test

💡 Ý nghĩa trong thực tế

  • Giúp chuẩn hoá cách nhóm phát triển tổ chức code (ví dụ như việc logging được làm thống nhất).
  • Giảm nguy cơ code lặp, gắn chặt, và rối loạn kiến trúc khi nhiều dev cùng làm việc.
  • Dễ dàng thay thế hoặc hoán đổi các thành phần mà không ảnh hưởng đến logic chính.

🔌 The Adapter Pattern – Mẫu Bộ Chuyển Đổi


🧭 Mục tiêu

  • Kết nối hai interface không tương thích mà không cần thay đổi code gốc.
  • Đóng vai trò như bộ chuyển đổi nguồn điện – giúp hệ thống dùng được “thiết bị” không cùng chuẩn.

📌 Khi nào nên dùng

Trường hợpGiải thích
🔄 Interface mismatchClient cần interface A, nhưng object chỉ hỗ trợ B
💼 Tích hợp hệ thống cũ (legacy code)Không sửa code cũ mà vẫn dùng được
🔧 Nâng cao khả năng tương tácLàm cho hai lớp vốn không tương thích có thể làm việc cùng nhau
🧪 Đảm bảo type safety trong TypeScriptĐặc biệt hữu ích khi bật strict mode

📐 UML Adapter Pattern

  • Client gọi callApiV1() thuộc interface ApiServiceV1
  • ApiClientV2 chỉ có callApiV2() – không tương thích
  • Adapter (ApiClientV2Adapter): cài đặt ApiServiceV1, và chuyển tiếp gọi sang ApiClientV2

🧱 Classic Implementation: Hệ đo chiều dài (feet ↔ meters)

🎯 Giao diện Metric:

interface MetricCalculator {
  getDistanceInMeters(): number;
}

class MetricSystem implements MetricCalculator {
  constructor(private distanceInMeters: number) {}
  getDistanceInMeters() {
    return this.distanceInMeters;
  }
}

🧮 Lớp cần tích hợp: Imperial

class ImperialSystem {
  constructor(private distanceInFeet: number) {}
  getDistanceInFeet() {
    return this.distanceInFeet;
  }
}

🔌 Adapter

class ImperialToMetricAdapter implements MetricCalculator {
  constructor(private imperialSystem: ImperialSystem) {}
  getDistanceInMeters(): number {
    return this.imperialSystem.getDistanceInFeet() * 0.3048;
  }
}

👨‍💻 Client code

class Reporter {
  static reportDistance(calculator: MetricCalculator) {
    console.log(`The distance is ${calculator.getDistanceInMeters()} meters.`);
  }
}
const metric = new MetricSystem(5);
const imperial = new ImperialSystem(10);
const adapter = new ImperialToMetricAdapter(imperial);

Reporter.reportDistance(metric);    // 5 meters
Reporter.reportDistance(adapter);   // 3.048 meters

🧪 Testing Adapter

  • ✅ Đảm bảo adapter trả đúng kết quả chuyển đổi
  • ✅ Có thể test bằng expect(...).toBeCloseTo(...) để kiểm tra số thập phân
  • ✅ Adapter tuân thủ interface MetricCalculator
npm run test --adaptor

⚠️ Criticism – Hạn chế

Hạn chếMô tả
🧱 Thêm nhiều lớp adapter → phức tạpMỗi interface không khớp là thêm một adapter
🐞 Debug khó hơnPhải kiểm tra qua lớp adapter để tìm bug
🐢 Có thể giảm hiệu năng (rất nhỏ)Nếu adapter làm nhiều việc chuyển đổi
🤯 Adapter không mô phỏng đúng behavior gốcDễ dẫn đến lỗi ngầm
🔄 Dễ bị “version mismatch” khi lớp gốc thay đổiAdapter không cập nhật kịp sẽ bị lỗi hoặc sai logic

💡 Gợi ý:

  • Dùng Partial Type, Mapped Type trong TS để giảm boilerplate
  • Giữ adapter đơn giản, rõ ràng và kiểm tra kỹ lưỡng

🌍 Real-world Examples

Hệ thốngAdapter được dùng ở đâu
Sequelize (ORM)Dùng adapter để kết nối MySQL, PostgreSQL, SQLite… qua dialect
React NativeDùng adapter để kết nối module Android / iOS với JS API
Filesystem adaptersThư viện đọc file dùng adapter để hỗ trợ local, S3, FTP…
Testing frameworksDùng adapter để hỗ trợ test runner khác nhau (Jest, Vitest, Mocha…)

🎨 Decorator Pattern – Mẫu trang trí

💡 Định nghĩa

Decorator là một mẫu thiết kế thuộc nhóm cấu trúc (structural), cho phép mở rộng hành vi của một đối tượng mà không cần sửa đổi trực tiếp lớp gốc.
Nó là giải pháp thay thế linh hoạt cho kế thừa, giúp mở rộng tính năng mà vẫn giữ nguyên cấu trúc ban đầu.

🔔 Ghi chú:
TypeScript 5 hỗ trợ decorators như một phần chuẩn ECMAScript – cho phép trang trí class, phương thức, thuộc tính – mở rộng phạm vi so với OOP truyền thống.


📌 Đặc điểm chính

  • 🛠 Mở rộng hành vi lúc runtime.
  • 🧱 Ưu tiên composition (kết hợp) thay vì inheritance (kế thừa).
  • 🔄 Có thể bọc (wrap) đệ quy nhiều lớp decorator.
  • 🧩 Các decorator giữ nguyên interface của đối tượng gốc → đảm bảo tính nhất quán.

📍 Khi nào nên dùng Decorator

  • 🎯 Cần gán thêm trách nhiệm mới cho đối tượng tại runtime.
  • 🚫 Tránh tạo quá nhiều subclass (class explosion).
  • 🧼 Mở rộng hành vi mà không sửa class gốc → tuân theo nguyên lý Open-Closed.
  • 🧾 Xử lý concerns cắt ngang (cross-cutting concerns) như log, cache, transaction. *
  • 🧠 Ứng xử có điều kiện (runtime behavior toggle). *

🧰 UML & Cài đặt mẫu

Cấu trúc UML gồm:

  • Component: Interface cơ bản
  • ConcreteComponent: Cài đặt ban đầu
  • Decorator: Lớp trừu tượng bọc component
  • ConcreteDecoratorA, ConcreteDecoratorB: Mở rộng behavior

🧪 Ví dụ: Hệ thống FileReader

interface FileReader {
  read(filePath: string): string
}
class SimpleFileReader implements FileReader {
  read(filePath: string): string {
    return `Content of ${filePath}`
  }
}
abstract class FileReaderDecorator implements FileReader {
  constructor(protected reader: FileReader) {}
  abstract read(filePath: string): string
}

➕ Thêm Encryption & Compression

class EncryptionDecorator extends FileReaderDecorator {
  read(filePath: string): string {
    const content = this.reader.read(filePath)
    return `Encrypted(${content})`
  }
}
class CompressionDecorator extends FileReaderDecorator {
  read(filePath: string): string {
    const content = this.reader.read(filePath)
    return `Compressed(${content})`
  }
}

🔄 Sử dụng

let reader: FileReader = new SimpleFileReader()
reader = new CompressionDecorator(reader)
reader = new EncryptionDecorator(reader)
console.log(reader.read("example.txt"))

🧪 Testing

  • ✅ Đảm bảo decorator không làm mất behavior gốc.
  • ✅ Behavior của decorator phải chính xác.
  • Thứ tự decorator quan trọng!
  • ✅ Kiểm tra side-effects nếu có (console.log, mutate state…)

⚠️ Khuyết điểm

  • 📉 Phụ thuộc chặt vào interface gốc.
  • 😵‍💫 Code có thể trở nên khó đọc nếu có nhiều lớp wrapper.
  • 🐢 Gây chậm nếu dùng trong hệ thống yêu cầu real-time.
  • 🔀 Sai thứ tự decorator có thể gây lỗi ngầm.

🌐 Ví dụ thực tế

  • Nest.js:
@Controller('dogs')
export class DogsController { 
   @Get() 
   findAll() { 
      return 'This action returns all dogs' 
   } 
} 

→ @Controller, @Get() là decorators giúp ánh xạ logic vào route.


🏢 Façade Pattern – Mẫu giao diện đơn giản hóa

💡 Định nghĩa

Façade là mẫu thiết kế giúp tạo giao diện đơn giản để ẩn đi sự phức tạp của hệ thống con. Giống như app điều khiển nhà thông minh giúp bạn bật đèn, điều hòa, mở cửa chỉ bằng 1 nút.


📌 Mục tiêu chính

  • 🧘‍♂️ Giảm độ phức tạp khi tương tác với hệ thống.
  • ✂️ Tách biệt client và hệ thống bên trong.
  • 🧼 Tạo lớp trừu tượng cấp cao → tăng khả năng bảo trì.

📍 Khi nào nên dùng

  • 🔄 Hệ thống backend phức tạp → cần facade để đơn giản hóa.
  • 🔍 Muốn ẩn chi tiết implement hệ thống con.
  • 🧱 Xây tầng (layering): Facade dùng làm entry point mỗi tầng.
  • 🧬 Giảm phụ thuộc giữa client ↔ subsystem.

🧰 Cài đặt TypeScript

interface SubsystemA {
  operationA1(): void
  operationA2(): void
}
interface SubsystemB {
  operationB1(): void
  operationB2(): void
}

class Facade {
  constructor(
    private subsystemA: SubsystemA,
    private subsystemB: SubsystemB
  ) {}
  simplifiedOperation1(): void {
    this.subsystemA.operationA1()
    this.subsystemB.operationB1()
  }
  simplifiedOperation2(): void {
    this.subsystemA.operationA2()
    this.subsystemB.operationB2()
    this.subsystemA.operationA1()
  }
}

🧪 Testing

  • ✅ Facade phải gọi đúng thứ tự các subsystem.
  • ✅ Không làm lộ phức tạp bên dưới cho client.
  • ✅ Có thể viết test mock cho từng subsystem để test Facade độc lập.

⚠️ Phê bình

  • 🐙 Dễ biến thành “God Object” nếu chứa quá nhiều logic.
  • 🧨 Khó mở rộng nếu mọi thứ dồn hết vào một facade.
  • ⚙️ Có thể gây mất tính linh hoạt do bị bó buộc vào giao diện đơn giản.

🌐 Ví dụ thực tế

class UserManagementFacade {
  private authService = new AuthService()
  private userProfileService = new UserProfileService()

  async login(username: string, password: string) {
    const token = await this.authService.login(username, password)
    const profile = await this.userProfileService.getUserProfile(username)
    return { token, profile }
  }
}

🎯 Composite Pattern – Mẫu Thiết Kế Thành Phần

📌 Ý tưởng chính

  • Mẫu thiết kế Composite cho phép xây dựng cây đối tượng có cùng giao diện, gồm các thành phần đơn lẻ (leaf) và thành phần phức tạp (composite).
  • Các đối tượng này cùng implement một interface để có thể xử lý thống nhất.

🧠 Ví dụ thực tế:

  • Hệ thống file: thư mục chứa được cả file và thư mục con. Ta có thể thực hiện thao tác như tính tổng dung lượng hay liệt kê nội dung mà không cần biết đó là file hay thư mục.

📌 Khi nào nên dùng Composite Pattern

  • Khi cần biểu diễn cấu trúc phân cấp (hierarchical) của đối tượng.
  • Khi muốn client không cần phân biệt giữa đối tượng đơn và tập hợp đối tượng.
  • Khi cần thao tác đồng nhất bất kể mức độ phức tạp của cấu trúc.

✅ Ưu điểm:

  • Tính mở rộng cao, dễ thêm/bớt thành phần.
  • Thống nhất cách gọi, kể cả với cấu trúc cây sâu.

🛠 UML Class Diagram & Triển khai

Interface chung:

interface FileSystemComponent {
  display(): string;
}

Lớp Leaf:

class File implements FileSystemComponent {
  constructor(private name: string) {}
  display(): string {
    return `File: ${this.name}`;
  }
}

Lớp Composite:

class Directory implements FileSystemComponent {
  private components: FileSystemComponent[] = [];
  constructor(private name: string) {}

  add(component: FileSystemComponent): void {
    this.components.push(component);
  }

  display(): string {
    return `Directory: ${this.name}\n` + this.components.map(c => c.display()).join('\n');
  }
}

Cách dùng:

const root = new Directory("Root");
const file1 = new File("file1.txt");
const file2 = new File("file2.txt");
const subDir = new Directory("Subdirectory");
const file3 = new File("file3.txt");

subDir.add(file3);
root.add(file1);
root.add(file2);
root.add(subDir);

console.log(root.display());

🧪 Testing

  • Viết test unit cho FileDirectory.
  • Viết integration test kiểm tra cấu trúc cây sau khi thêm nhiều node.

❌ Nhược điểm & Lưu ý

  • Interface quá tổng quát → các leaf bị ép implement các method không cần thiết như add().
  • Performance thấp nếu cấu trúc cây quá sâu.
  • Khó kiểm soát loại thành phần được thêm vào.
  • Vòng lặp vô hạn nếu thiết kế không đúng (gây cycle).

🌍 Use Case thực tế

  • DOM Tree trong HTML.
  • UI Component Tree trong các thư viện như React, Angular.

🎯 Proxy Pattern – Mẫu Thiết Kế Đại Diện

📌 Ý tưởng chính

Proxy cho phép gọi phương thức của một đối tượng thông qua một đối tượng khác (được gọi là đại diện hoặc “proxy object”).
Proxy thường dùng để kiểm soát truy cập, lazy load, caching, logging, bảo mật, v.v.

🧠 Ví dụ thực tế:

  • Thư ký nhận điện thoại thay giám đốc, lọc cuộc gọi trước khi chuyển.
  • Vue.js, MobX, ORM sử dụng Proxy để theo dõi thay đổi hoặc lazy loading.

📌 Khi nào nên dùng Proxy Pattern

Tình huống dùng ProxyMô tả
🐌 Lazy InitializationKhởi tạo chậm các đối tượng nặng
🔐 Access ControlKiểm soát quyền truy cập
📜 Logging & AuditGhi log khi gọi method
⚡ CachingLưu kết quả để tránh tính toán lặp lại
✅ Validation & RetryKiểm tra dữ liệu trước khi gọi method thật

🛠 UML Class Diagram & Cấu trúc

Interface chung:

export interface Store {
  save(data: string): void;
}

Lớp thực:

export class TextStore implements Store {
  save(data: string): void {
    console.log(`TextStore: saving "${data}"`);
  }
}

Lớp Proxy:

export class ProxyTextStore implements Store {
  constructor(private textStore?: TextStore) {}

  save(data: string): void {
    console.log(`ProxyTextStore: preparing to save "${data}"`);
    if (!this.textStore) {
      console.log("ProxyTextStore: Lazy init TextStore");
      this.textStore = new TextStore();
    }
    this.textStore.save(data);
  }
}

🚀 Cách dùng

// Direct usage
const direct = new TextStore();
direct.save("Direct data");

// Proxy usage (lazy init)
const proxy = new ProxyTextStore();
proxy.save("Proxy data 1"); // init here
proxy.save("Proxy data 2"); // reused

// Proxy with pre-initialized object
const preInit = new ProxyTextStore(new TextStore());
preInit.save("Pre-initialized data");

🧪 Testing

  • Kiểm tra lazy initialization có diễn ra không.
  • Kiểm tra Proxy có gọi đúng phương thức của real object.
  • Gợi ý: dùng mock object/log để theo dõi chuỗi gọi hàm.

Lệnh chạy test:

$ npm run test --proxy

❌ Nhược điểm & Lưu ý

Nhược điểmMô tả
🐢 Tăng độ trễGọi qua thêm một lớp nên chậm hơn
🧩 Phức tạp hoá codeĐặc biệt nếu lồng nhiều proxy
🧼 Quản lý vòng đời khóProxy cần kiểm soát vòng đời của object thực
🧪 Khó kiểm thửCần test cả proxy lẫn real object, khó mock
🧱 Khó debugProxy chặn lỗi, gây khó trace khi có lỗi thực tế

🌍 Use Case thực tế

Ứng dụngMô tả
Vue.jsDùng Proxy để reactive data
MobXProxy theo dõi thay đổi trạng thái
ORMLazy load các bản ghi liên kết
Image loadingChỉ tải ảnh thật khi sắp vào viewport
Logging wrapperProxy thêm log cho mỗi hành động ghi dữ liệu

🌉 Bridge Pattern – Mẫu Thiết Kế Cầu Nối

📌 Ý tưởng chính

Bridge Pattern tách abstraction (khái niệm trừu tượng) khỏi implementation (cách hiện thực) để chúng có thể phát triển độc lập.

💡 Ví dụ thực tế:

  • Remote điều khiển (abstraction) có thể điều khiển TV, Loa, Máy lạnh (implementation) — mỗi loại có cách hoạt động khác nhau nhưng chia sẻ cùng 1 giao diện điều khiển.

📌 Khi nào nên dùng Bridge Pattern

Trường hợp dùngMô tả
✂️ Tách riêng abstraction & implementationGiúp code dễ mở rộng & bảo trì
🧩 Khi muốn mở rộng cả hai chiềuAbstraction & Implementation đều có thể thêm mới
🔄 Chuyển đổi implementation lúc runtimeVí dụ: thay đổi cơ chế tưới cây tùy thời tiết
♻️ Dùng chung logic implementNhiều abstraction dùng chung một implement mà không dùng kế thừa phức tạp

🧠 UML & Cấu trúc

Interface của phần “Implementation”:

interface WateringMechanism {
  water(amount: number): void;
  checkWaterLevel(): number;
  refill(amount: number): void;
}

Abstraction (SmartPlantCare):

abstract class SmartPlantCare {
  protected mechanism: WateringMechanism;
  protected moistureThreshold: number;

  constructor(mechanism: WateringMechanism, threshold: number) {
    this.mechanism = mechanism;
    this.moistureThreshold = threshold;
  }

  abstract waterPlant(currentMoisture: number): void;
  abstract adjustWatering(weatherForecast: string): void;
}

🧪 Implement cụ thể

Cơ chế tưới – MistSprayer

class MistSprayer implements WateringMechanism {
  private waterReservoir: number = 500;

  water(amount: number): void {
    this.waterReservoir -= amount;
    console.log(`Misting ${amount}ml of water`);
  }

  checkWaterLevel(): number {
    return this.waterReservoir;
  }

  refill(amount: number): void {
    this.waterReservoir += amount;
    console.log(`Refilled ${amount}ml of water`);
  }
}

Loại cây cụ thể – TropicalPlantCare

class TropicalPlantCare extends SmartPlantCare {
  constructor(mechanism: WateringMechanism) {
    super(mechanism, 60);
  }

  waterPlant(currentMoisture: number): void {
    if (currentMoisture < this.moistureThreshold) {
      this.mechanism.water(100);
    } else {
      console.log("Tropical plant doesn't need watering");
    }
  }

  adjustWatering(weatherForecast: string): void {
    if (weatherForecast.includes("humidity")) {
      this.moistureThreshold += 10;
      console.log("Adjusted for humid weather");
    } else if (weatherForecast.includes("dry")) {
      this.moistureThreshold -= 10;
      console.log("Adjusted for dry weather");
    }
  }
}

📋 Ưu điểm & Nhược điểm

✅ Ưu điểm:

  • Tách rời logic & hiện thực
  • Mở rộng độc lập abstraction hoặc implement
  • Dễ maintain và test
  • Thay đổi lúc runtime

❌ Nhược điểm:

Vấn đềMô tả
🧠 Khó hiểu hơnGây rối với team chưa quen với pattern
🧱 Overkill nếu chỉ có 1 implementKhông cần cầu khi chỉ có 1 bờ
🧩 Tăng số lớpAbstraction + implement => phức tạp hóa codebase

🌍 Use Case thực tế

Use CaseMô tả
LoggerTách phần log (abstraction) khỏi phần lưu trữ (console, file, cloud…)
UI renderingUI component dùng các render engine khác nhau: WebGL, Canvas
Smart homeĐiều khiển cây, đèn, máy lạnh, mỗi loại dùng cơ chế khác nhau nhưng cùng interface

Ví dụ Logger:

interface Appender {
  append(message: string): void;
}

abstract class Logger {
  constructor(protected appender: Appender) {}
  abstract log(message: string): void;
}

class ConsoleAppender implements Appender {
  append(msg: string): void {
    console.log(`[Console] ${msg}`);
  }
}

class DebugLogger extends Logger {
  log(msg: string): void {
    this.appender.append(`DEBUG: ${msg}`);
  }
}

🧪 Testing

  • Test riêng WateringMechanism với các implement khác nhau.
  • Test logic SmartPlantCare với nhiều kiểu cơ chế (Mist, Drip…).
  • Test tính năng thay đổi hành vi theo weatherForecast.

Chạy test:

$ npm run test --bridge

🎯 Flyweight Pattern là gì?

Flyweight là một mẫu thiết kế thuộc nhóm Structural, dùng để tối ưu hóa bộ nhớ bằng cách chia sẻ trạng thái chung giữa các object “nặng ký” thay vì tạo mới từng cái.
Nó cực kỳ hữu ích trong các hệ thống có rất nhiều đối tượng tương tự nhau (game, editor, web hiệu năng cao…).


🧠 Ý tưởng chính

  • Trạng thái nội tại (Intrinsic): là phần dữ liệu dùng chung giữa nhiều đối tượng (ví dụ: màu xe, hãng xe…).
  • Trạng thái bên ngoài (Extrinsic): là phần dữ liệu duy nhất cho từng đối tượng cụ thể (ví dụ: biển số, chủ xe…).

🧥 Ví dụ hình ảnh: Giống như nhiều vũ công dùng chung một số bộ trang phục truyền thống đắt tiền. Ai diễn thì lấy mặc, ai không diễn thì không cần tạo mới đồ.


Khi nào nên dùng Flyweight Pattern?

  • Khi hệ thống có số lượng lớn object giống nhau và việc tạo mới mỗi cái là tốn kém tài nguyên.
  • Khi muốn giảm thiểu việc tạo mới object bằng cách dùng lại từ cache.
  • Khi bộ nhớ giới hạn hoặc muốn giảm áp lực lên Garbage Collector.
  • Khi có thể phân tách được trạng thái chung (intrinsic)trạng thái riêng biệt (extrinsic) của object.

🔧 UML và thành phần chính

📦 Các thành phần:

  • Flyweight (interface): Định nghĩa phương thức perform, nhận tham số ngoại sinh.
  • ConcreteFlyweight: Cài đặt Flyweight, có dữ liệu nội tại (shared state).
  • FlyweightFactory: Quản lý cache và tái sử dụng các flyweight.
  • Client: Luôn tương tác qua factory thay vì tạo object trực tiếp.

💡 Factory kiểm tra cache → Nếu có thì tái sử dụng, nếu không thì tạo mới.


💻 Ví dụ TypeScript

export interface Flyweight {
  perform(customization: { id: string }): void
}
export class ConcreteFlyweight implements Flyweight {
  constructor(private sharedState: Object) {}
  public perform(customization: { id: string }): void {
    console.log(
      `Shared: ${JSON.stringify(this.sharedState)}, Unique: ${customization.id}`
    )
  }
}

Factory với QuickLRU:

import QuickLRU from 'quick-lru';
export class FlyweightFactory {
  private cache = new QuickLRU({ maxSize: 1000 });
  public getFlyweight(sharedState: Object): Flyweight {
    const key = JSON.stringify(sharedState);
    if (!this.cache.has(key)) {
      console.log("Creating new flyweight.");
      this.cache.set(key, new ConcreteFlyweight(sharedState));
    } else {
      console.log("Reusing flyweight.");
    }
    return this.cache.get(key)!;
  }
}

Client sử dụng:

tsCopyEditconst factory = new FlyweightFactory()
function addCar(plates: string, owner: string, brand: string, model: string, color: string) {
  const flyweight = factory.getFlyweight({ brand, model, color });
  flyweight.perform({ id: `${plates}_${owner}` });
}

🧪 Testing cần kiểm tra gì?

  • ✅ Flyweight được dùng lại (so sánh reference).
  • ✅ Đúng hành vi cho perform với tham số ngoài (extrinsic).
  • ✅ Factory tạo đúng object khi chưa có trong cache.
$ npm run test --flyweight

⚠️ Criticisms – Hạn chế của Flyweight

🧱 Vấn đề📌 Ghi chú
Tăng độ phức tạpViệc tách intrinsic/extrinsic đôi khi làm code khó đọc hơn
Tối ưu sớm (premature optimization)Đôi khi không đáng dùng nếu không thực sự cần
Khó thay đổi shared stateVì shared giữa nhiều object → thay đổi có thể gây bug diện rộng
Độ linh hoạt hạn chếKhông phù hợp cho những hệ thống ít lặp lại đối tượng

🌐 Ứng dụng thực tế

📄 Text Editor: Mỗi ký tự không cần giữ riêng thông tin về font/size/color. Thay vào đó, chia sẻ cùng một flyweight cho các ký tự có cùng định dạng.

interface CharacterFlyweight {
    render(position: { x: number, y: number }): void;
}
class CharacterFlyweightFactory {
  private characters = new Map<string, CharacterFlyweight>();
  getCharacter(char: string, font: string, size: number, color: string): CharacterFlyweight {
    const key = `${char}-${font}-${size}-${color}`;
    if (!this.characters.has(key)) {
      this.characters.set(key, new CharacterFlyweightImpl(char, font, size, color));
    }
    return this.characters.get(key)!;
  }
}

Để lại một bình luận