🐖with Modern Backend Development

Design Patterns for Modern Backend Development – with Example Use Cases

MVC (Model-View-Controller) Pattern

Overview

Mô hình Model-View-Controller (MVC) là một mẫu thiết kế được sử dụng rộng rãi trong phát triển backend hiện đại. Nó cung cấp cách phân tách tầng trình bày (View) khỏi tầng business logic và lưu trữ dữ liệu (Model và Controller). Sự phân tách này giúp các lập trình viên viết mã mô-đun hơn và dễ dàng bảo trì.

Trong mô hình MVC, Model đại diện cho dữ liệu và business logic của ứng dụng. Controller hoạt động như một trung gian giữa Model và View, xử lý đầu vào từ người dùng và cập nhật Model tương ứng. View chịu trách nhiệm trình bày dữ liệu cho người dùng và nhận đầu vào từ người dùng.

Một trong các ưu điểm chính của việc sử dụng mô hình MVC là cho phép dễ dàng kiểm tra và bảo trì mã nguồn. Vì Model và Controller được tách ra khỏi View, nên có thể kiểm tra và sửa đổi từng thành phần một cách độc lập.

Lợi ích khác của việc sử dụng mô hình MVC là cho phép tái sử dụng mã. Model và Controller có thể được tái sử dụng trong các View khác nhau, cung cấp một cách tiếp cận mô-đun hơn với phát triển phần mềm.

Nhìn chung, mô hình MVC là một công cụ hữu ích để tạo ra các hệ thống backend có thể mở rộng, dễ bảo trì và hiệu quả. Nó phân tách các mối quan tâm và cho phép một cách tiếp cận mô-đun hơn trong phát triển phần mềm, giúp dễ dàng kiểm tra, bảo trì và sửa đổi mã nguồn.

Example of using the MVC pattern in a web application

Đầu tiên, chúng ta sẽ xem xét một ví dụ về việc sử dụng mô hình MVC trong một ứng dụng web với framework Node.js phổ biến, Express.js.

Trong một ứng dụng Express.js, thành phần Model thường được thực hiện bằng cách sử dụng cơ sở dữ liệu như MongoDB hoặc MySQL. Thành phần View thường được thực hiện bằng cách sử dụng các công cụ tạo mẫu như EJS hoặc Handlebars. Thành phần Controller được thực hiện bằng cách sử dụng các hàm phần mềm trung gian (middleware), các hàm được thực thi theo một thứ tự cụ thể khi có yêu cầu gửi đến máy chủ.

Ví dụ, khi người dùng gửi yêu cầu để xem một bài đăng trên blog, yêu cầu được xử lý bởi thành phần Controller. Controller lấy bài viết từ thành phần Model và truyền nó sang thành phần View, nơi hiển thị nó bằng cách sử dụng một công cụ tạo mẫu. HTML kết quả sau đó được gửi trả lại trình duyệt của người dùng.

Sử dụng mô hình MVC trong một ứng dụng web có thể mang lại nhiều lợi ích, bao gồm khả năng mở rộng, bảo trì và kiểm tra được cải thiện. Bằng cách tách ứng dụng thành các thành phần riêng biệt, các nhà phát triển có thể thay đổi một thành phần mà không ảnh hưởng đến các thành phần khác. Điều này giúp dễ dàng bảo trì và kiểm tra ứng dụng theo thời gian.

Trong ví dụ sau đây, Model được thể hiện bởi lớp PostModel, có trách nhiệm lấy và lưu dữ liệu vào cơ sở dữ liệu.

View được thể hiện bởi lớp PostView, có trách nhiệm hiển thị trang HTML và xử lý đầu vào từ người dùng.

Controller được thể hiện bởi lớp PostController, hoạt động như một trung gian giữa Model và View. Nó khởi tạo View, xử lý đầu vào từ người dùng và cập nhật View dựa trên các thay đổi của Model.

// the model
interface Post {
  id: number;
  title: string;
  content: string;
  date: Date;
}

class PostModel {
  private posts: Post[] = [];

  getPosts() {
    // fetch posts from database
    return this.posts;
  }

  addPost(post: Post) {
    // save post to database
    this.posts.push(post);
  }
}
// the view
class PostView {
  displayPosts(posts: Post[]) {
    // render posts to the HTML page
  }

  getPostFromInput(): Post {
    // retrieve input values from the HTML page
    // and create a new Post object
  }
}
// the controller
class PostController {
  private model: PostModel;
  private view: PostView;

  constructor(model: PostModel, view: PostView) {
    this.model = model;
    this.view = view;
  }

  init() {
    // initialize the view
    this.view.displayPosts(this.model.getPosts());
  }

  addPost() {
    // get new post from view
    const post = this.view.getPostFromInput();
    // add post to model
    this.model.addPost(post);
    // update view
    this.view.displayPosts(this.model.getPosts());
  }
}

Việc triển khai mẫu MVC này cho phép tách biệt các mối quan tâm và tính mô đun trong web application code, khiến việc bảo trì và mở rộng dễ dàng hơn theo thời gian.

Repository Pattern

Overview

Mẫu Repository là một mẫu thiết kế cung cấp một lớp trừu tượng giữa lớp truy cập dữ liệu và phần còn lại của ứng dụng. Nó tách riêng logic truy xuất dữ liệu từ lớp lưu trữ dữ liệu, cung cấp một phương pháp phát triển phần mềm mô-đun hơn.

Trong mẫu Repository, một kho lưu trữ hoạt động như một người trung gian giữa lớp lưu trữ dữ liệu và lớp logic ứng dụng. Nó cung cấp một điểm nhập duy nhất để truy xuất và thao tác dữ liệu, cho phép phần còn lại của ứng dụng tách rời khỏi các chi tiết cụ thể của lớp lưu trữ dữ liệu. Điều này làm cho việc thay đổi lớp lưu trữ dữ liệu dễ dàng hơn mà không ảnh hưởng đến phần còn lại của ứng dụng.

Một trong những lợi thế chính của việc sử dụng mẫu Repository là nó cho phép một phương pháp phát triển phần mềm mô-đun hơn. Lớp logic ứng dụng được tách ra khỏi lớp lưu trữ dữ liệu, làm cho việc kiểm tra và bảo trì từng thành phần riêng lẻ dễ dàng hơn. Điều này cũng làm cho việc tái sử dụng lớp logic ứng dụng với các lớp lưu trữ dữ liệu khác nhau trở nên dễ dàng hơn.

Một lợi ích khác của việc sử dụng mẫu Repository là nó có thể cải thiện hiệu suất bằng cách giảm số lượng lần gọi đến lớp lưu trữ dữ liệu. Do logic truy cập dữ liệu được đóng gói trong kho lưu trữ, nên có thể tối ưu hóa truy vấn và giảm số lượng lần gọi cơ sở dữ liệu.

Nhìn chung, mẫu Repository là một công cụ hữu ích để tạo ra các hệ thống phía sau linh hoạt, dễ bảo trì và hiệu quả. Nó tách biệt các mối quan tâm và cho phép một phương pháp phát triển phần mềm mô-đun hơn, giúp việc kiểm tra, bảo trì và sửa đổi cơ sở mã dễ dàng hơn. Nó cũng có thể cải thiện hiệu suất bằng cách giảm số lượng lần gọi đến lớp lưu trữ dữ liệu.

Example of using the Repository pattern with a database

Mẫu Repository là một mẫu thiết kế cung cấp một lớp trừu tượng giữa lớp truy cập dữ liệu và phần còn lại của ứng dụng. Trong mẫu Repository, cơ sở dữ liệu được biểu diễn dưới dạng một tập hợp các đối tượng, với mỗi đối tượng đại diện cho một bảng hoặc bộ sưu tập trong cơ sở dữ liệu. Lớp Repository cung cấp một tập hợp các phương thức để tương tác với cơ sở dữ liệu, chẳng hạn như tạo, đọc, cập nhật và xóa các đối tượng.

Ví dụ, giả sử chúng ta có một ứng dụng Express lưu trữ thông tin về sách trong cơ sở dữ liệu. Chúng ta có thể tạo ra Book model, định nghĩa các field và behavior của một book object. Sau đó, chúng ta có thể tạo ra một lớp BookRepository cung cấp các phương thức để tạo, đọc, cập nhật và xóa các đối tượng sách trong cơ sở dữ liệu.

Trong lớp BookRepository, chúng ta có thể định nghĩa các phương thức như get_all_booksget_book_by_id để lấy các đối tượng sách từ cơ sở dữ liệu. Chúng ta cũng có thể định nghĩa các phương thức như create_bookupdate_book để thêm hoặc sửa đổi các book object trong cơ sở dữ liệu.

Sử dụng mẫu Repository với cơ sở dữ liệu có thể mang lại nhiều lợi ích, bao gồm cải thiện khả năng kiểm tra, bảo trì và linh hoạt.

Bằng cách trừu tượng hóa lớp truy cập cơ sở dữ liệu khỏi phần còn lại của ứng dụng, các nhà phát triển có thể dễ dàng chuyển đổi giữa các công nghệ cơ sở dữ liệu khác nhau hoặc thay đổi lược đồ cơ sở dữ liệu mà không ảnh hưởng đến phần còn lại của ứng dụng. Ngoài ra, bằng cách cung cấp một tập hợp các phương thức để tương tác với cơ sở dữ liệu, mẫu Repository có thể giúp việc viết các bài kiểm tra cho ứng dụng trở nên dễ dàng hơn.

// Define a Book interface that represents a book object
interface Book {
  id: number;
  title: string;
  author: string;
  publishedDate: Date;
}

// Define a BookRepository class that provides methods for interacting with a database of books
class BookRepository {
  private db: any; // Database connection object

  constructor(db: any) {
    this.db = db;
  }

  // Get all books from the database
  async getAllBooks(): Promise<Book[]> {
    const result = await this.db.query('SELECT * FROM books');
    return result.rows;
  }

  // Get a book by its ID
  async getBookById(id: number): Promise<Book> {
    const result = await this.db.query('SELECT * FROM books WHERE id = $1', [id]);
    return result.rows[0];
  }

  // Create a new book in the database
  async createBook(book: Book): Promise<void> {
    await this.db.query('INSERT INTO books (title, author, published_date) VALUES ($1, $2, $3)', [book.title, book.author, book.publishedDate]);
  }

  // Update an existing book in the database
  async updateBook(id: number, book: Book): Promise<void> {
    await this.db.query('UPDATE books SET title = $1, author = $2, published_date = $3 WHERE id = $4', [book.title, book.author, book.publishedDate, id]);
  }

  // Delete a book from the database
  async deleteBook(id: number): Promise<void> {
    await this.db.query('DELETE FROM books WHERE id = $1', [id]);
  }
}

// Example usage of the BookRepository class
const db = new Database(); // Instantiate a database connection object
const bookRepository = new BookRepository(db); // Instantiate a BookRepository object
const books = await bookRepository.getAllBooks(); // Get all books from the database
const book = await bookRepository.getBookById(1); // Get a book by its ID
const newBook = { title: 'New Book', author: 'Jane Doe', publishedDate: new Date() };
await bookRepository.createBook(newBook); // Create a new book in the database
await bookRepository.updateBook(1, { title: 'Updated Book', author: 'John Smith', publishedDate: new Date() }); // Update an existing book in the database
await bookRepository.deleteBook(1); // Delete a book from the database

Dependency Injection Pattern

Overview

Mẫu Dependency Injection (DI) là một mẫu thiết kế cho phép tạo ra các thành phần phần mềm không ràng buộc chặt chẽ. Nó được sử dụng để giảm bớt sự ràng buộc giữa các thành phần và cải thiện tính linh hoạt, khả năng kiểm thử và khả năng bảo trì của mã nguồn.

Trong mẫu Dependency Injection, các phụ thuộc được tiêm vào một thành phần thay vì được tạo trong thành phần đó. Điều này cho phép các thành phần được tạo ra độc lập với các phụ thuộc của chúng, giúp dễ dàng thay thế hoặc sửa đổi các phụ thuộc mà không ảnh hưởng đến thành phần chính.

Có ba loại chính của Dependency Injection: Constructor Injection (Tiêm qua hàm tạo), Property Injection (Tiêm qua thuộc tính) và Method Injection (Tiêm qua phương thức).

Constructor Injection liên quan đến việc truyền các phụ thuộc vào một thành phần thông qua hàm tạo của nó. Property Injection liên quan đến việc đặt các phụ thuộc thông qua các thuộc tính công khai của thành phần. Method Injection liên quan đến việc truyền các phụ thuộc vào các phương thức của thành phần.

Một trong những lợi ích chính của việc sử dụng mẫu Dependency Injection là nó cải thiện khả năng kiểm thử của mã nguồn. Bằng cách tiêm các phụ thuộc vào một thành phần, ta có thể tạo các bài kiểm tra đơn vị (unit tests) cô lập thành phần khỏi các phụ thuộc của nó, giúp dễ dàng kiểm thử thành phần đó một cách độc lập.

Một lợi ích khác của việc sử dụng mẫu Dependency Injection là nó làm mã nguồn linh hoạt và dễ bảo trì hơn. Bằng cách giảm sự ràng buộc giữa các thành phần, ta dễ dàng sửa đổi hoặc thay thế các thành phần mà không ảnh hưởng đến phần còn lại của ứng dụng.

Tổng quát, mẫu Dependency Injection là một công cụ hữu ích để tạo ra các hệ thống backend có khả năng mở rộng, dễ bảo trì và hiệu quả. Nó giảm sự ràng buộc giữa các thành phần và cải thiện tính linh hoạt, khả năng kiểm thử và khả năng bảo trì của mã nguồn.

Example of using the Dependency Injection pattern for decoupling dependencies

Trong ngữ cảnh phát triển backend, chúng ta có thể sử dụng Dependency Injection để tách rời các thành phần của ứng dụng khỏi việc triển khai cụ thể của các dịch vụ hoặc thư viện bên ngoài, chẳng hạn như cơ sở dữ liệu, bộ nhớ cache hoặc nhà cung cấp email. Điều này cho phép chúng ta dễ dàng chuyển đổi giữa các triển khai khác nhau của các dịch vụ này hoặc tạo giả đối tượng (mock) chúng trong quá trình kiểm thử.

Dưới đây là một ví dụ về việc sử dụng Dependency Injection trong một ứng dụng TypeScript tương tác với cơ sở dữ liệu:

// Define an interface for a database connection object
interface DatabaseConnection {
  query(sql: string, params?: any[]): Promise<any>;
}

// Define a class for a PostgreSQL database connection
class PostgresConnection implements DatabaseConnection {
  private client: any; // PostgreSQL client object

  constructor() {
    this.client = new PostgreSQLClient(); // Instantiate a PostgreSQL client object
    this.client.connect(); // Connect to the database
  }

  async query(sql: string, params?: any[]): Promise<any> {
    const result = await this.client.query(sql, params);
    return result.rows;
  }
}

// Define a class for a BookService that depends on a database connection
class BookService {
  private db: DatabaseConnection; // Database connection object

  constructor(db: DatabaseConnection) {
    this.db = db;
  }

  async getAllBooks(): Promise<Book[]> {
    const result = await this.db.query('SELECT * FROM books');
    return result.map((row: any) => ({ id: row.id, title: row.title, author: row.author, publishedDate: row.published_date }));
  }

  async getBookById(id: number): Promise<Book> {
    const result = await this.db.query('SELECT * FROM books WHERE id = $1', [id]);
    return { id: result.id, title: result.title, author: result.author, publishedDate: result.published_date };
  }

  async createBook(book: Book): Promise<void> {
    await this.db.query('INSERT INTO books (title, author, published_date) VALUES ($1, $2, $3)', [book.title, book.author, book.publishedDate]);
  }

  async updateBook(id: number, book: Book): Promise<void> {
    await this.db.query('UPDATE books SET title = $1, author = $2, published_date = $3 WHERE id = $4', [book.title, book.author, book.publishedDate, id]);
  }

  async deleteBook(id: number): Promise<void> {
    await this.db.query('DELETE FROM books WHERE id = $1', [id]);
  }
}

// Example usage of the BookService class with a PostgresConnection object
const db = new PostgresConnection(); // Instantiate a PostgresConnection object
const bookService = new BookService(db); // Instantiate a BookService object with the PostgresConnection object as its dependency
const books = await bookService.getAllBooks(); // Get all books from the database
const book = await bookService.getBookById(1); // Get a book by its ID
const newBook = { title: 'New Book', author: 'Jane Doe', publishedDate: new Date() };
await bookService.createBook(newBook); // Create a new book in the database
await bookService.updateBook(1, { title: 'Updated Book', author: 'John Smith', publishedDate: new Date() }); //

Trong Spring Boot, DI là việc các Object nên phụ thuộc vào các abstract class và instance chi tiết của nó sẽ được Inject vào đối tượng lúc runtime.

Observer Pattern

Overview

Mẫu Observer là một mẫu thiết kế cho phép một object (gọi là subject) thông báo cho các object khác (gọi là observers) khi trạng thái của nó thay đổi. Nó cung cấp một cách cho các object giao tiếp với nhau mà không cần biết trực tiếp sự tồn tại của nhau.

Trong mẫu Observer, subject duy trì một danh sách các observers và thông báo cho chúng khi trạng thái của nó thay đổi. Các observers sau đó có thể thực hiện hành động dựa trên sự thay đổi trong trạng thái của subject. Điều này cho phép mối quan hệ giữa subject và observers là lỏng lẻo, làm cho việc sửa đổi hoặc mở rộng hệ thống dễ dàng hơn.

Một trong những lợi ích chính của việc sử dụng mẫu Observer là nó cải thiện tính mô-đun và tính linh hoạt của mã nguồn. Bằng cách tách biệt subject và observers, ta có thể thêm hoặc xóa observers mà không ảnh hưởng đến subject, hoặc thêm các subject mới mà không ảnh hưởng đến observers hiện có.

Một lợi ích khác của việc sử dụng mẫu Observer là nó có thể cải thiện hiệu suất của hệ thống. Bằng cách thông báo chỉ cho các observers quan tâm đến sự thay đổi, ta có thể giảm số lượng thông báo và cải thiện hiệu suất tổng thể của hệ thống.

Example of using the Observer pattern for event-driven programming

Giả sử chúng ta có một ứng dụng web cho phép người dùng đăng ký các chủ đề quan tâm khác nhau. Khi có nội dung mới được thêm vào chủ đề đã đăng ký, người dùng sẽ nhận được thông báo. Chúng ta có thể triển khai tính năng này bằng cách sử dụng mẫu Observer.

Đầu tiên, chúng ta định nghĩa giao diện subject Topic, sẽ thông báo cho các observers (subscribers) về bất kỳ cập nhật nào:

interface Topic {
  subscribe(observer: Observer): void;
  unsubscribe(observer: Observer): void;
  notify(): void;
}

Sau đó, chúng ta triển khai giao diện Topic trong một lớp chủ đề cụ thể là TopicManager, quản lý một danh sách các người đăng ký và thông báo cho họ mỗi khi có nội dung mới được thêm vào:

class TopicManager implements Topic {
  private subscribers: Observer[] = [];

  public subscribe(observer: Observer): void {
    this.subscribers.push(observer);
  }

  public unsubscribe(observer: Observer): void {
    const index = this.subscribers.indexOf(observer);
    if (index !== -1) {
      this.subscribers.splice(index, 1);
    }
  }

  public notify(): void {
    for (const subscriber of this.subscribers) {
      subscriber.update();
    }
  }

  public addContent(topic: string, content: string): void {
    // Add new content to the topic
    // ...

    // Notify all subscribers of the new content
    this.notify();
  }
}

Tiếp theo, chúng ta định nghĩa observer interface, có 1 update method sẽ được gọi bởi subject:

interface Observer {
  update(): void;
}

Chúng ta triển khai Observer interface trong concrete observer class, User, lớp này sẽ nhận được thông báo khi nội dung mới được thêm vào chủ đề đã đăng kí:

class User implements Observer {
  private readonly username: string;

  constructor(username: string) {
    this.username = username;
  }

  public update(): void {
    console.log(`[${this.username}] New content has been added to a subscribed topic`);
  }
}

Cuối cùng, chúng ta có thể sử dụng TopicManager and User classes để triển khai features đăng ký:

// Create a new topic manager
const topicManager = new TopicManager();

// Create two users
const user1 = new User("Alice");
const user2 = new User("Bob");

// Subscribe the users to a topic
topicManager.subscribe(user1);
topicManager.subscribe(user2);

// Add new content to the topic
topicManager.addContent("science", "New scientific discovery!");

// Output:
// [Alice] New content has been added to a subscribed topic
// [Bob] New content has been added to a subscribed topic

Trong ví dụ này, TopicManager đóng vai trò là chủ đề (subject) và lớp User đóng vai trò là người quan sát (observer).

TopicManager duy trì một danh sách các người đăng ký (subscribers) và thông báo cho họ mỗi khi có nội dung mới được thêm vào chủ đề mà họ đã đăng ký. Lớp User nhận thông báo và thực hiện một số hành động, chẳng hạn như hiển thị thông báo cho người dùng.

Mô hình Observer cho phép chúng ta tách rời chủ đề và người quan sát, giúp dễ dàng thêm hoặc xóa các người đăng ký mà không ảnh hưởng đến phần còn lại của hệ thống.

Decorator Pattern

Mô hình Decorator là một mô hình thiết kế cho phép thêm hành vi vào một đối tượng cá nhân, cả tĩnh và động, mà không ảnh hưởng đến hành vi của các đối tượng khác cùng lớp. Nó được sử dụng để thêm chức năng vào các đối tượng trong thời gian chạy, thay vì thời gian biên dịch.

Trong mô hình Decorator, một lớp decorator được sử dụng để bao bọc đối tượng gốc. Lớp decorator có cùng giao diện với đối tượng gốc, cho phép sử dụng nó theo cùng cách. Lớp decorator sau đó thêm hành vi vào đối tượng gốc bằng cách chuyển giao một phần công việc cho đối tượng được bao bọc và thêm hành vi riêng của mình.

Một trong những lợi ích chính của việc sử dụng mô hình Decorator là cho phép thêm chức năng vào các đối tượng một cách động. Điều này có thể hữu ích trong những tình huống mà hành vi của một đối tượng cần được thay đổi trong thời gian chạy, hoặc khi hành vi của một đối tượng cần được mở rộng mà không thay đổi giao diện của nó.

Lợi ích khác của việc sử dụng mô hình Decorator là nó có thể cải thiện khả năng bảo trì của mã nguồn. Vì hành vi của đối tượng được phân tách thành các decorator riêng lẻ, nên dễ dàng chỉnh sửa hoặc mở rộng hành vi của đối tượng mà không ảnh hưởng đến các phần khác của hệ thống.

Tổng thể, mô hình Decorator là một công cụ hữu ích để tạo ra các hệ thống backend có khả năng mở rộng, dễ bảo trì và hiệu quả. Nó cho phép thêm chức năng vào các đối tượng một cách động, cải thiện tính linh hoạt và khả năng bảo trì của mã nguồn.

Example of using the Decorator pattern for adding functionality to a class dynamically

Giả sử chúng ta có 1 class Car đại diện cho 1 basic car với các properties and methods:

class Car {
  private make: string;
  private model: string;
  private year: number;
  private price: number;

  constructor(make: string, model: string, year: number, price: number) {
    this.make = make;
    this.model = model;
    this.year = year;
    this.price = price;
  }

  public getMake(): string {
    return this.make;
  }

  public getModel(): string {
    return this.model;
  }

  public getYear(): number {
    return this.year;
  }

  public getPrice(): number {
    return this.price;
  }
}

Chúng ta sử dụng mô hình Decorator để thêm các chức năng bổ sung vào lớp Car, chẳng hạn như tính toán thuế bán hàng trên giá xe và thêm một số tính năng tùy chọn cho xe, như hệ thống định vị và cửa sổ trời. Chúng ta có thể thêm các tính năng này vào lớp Car một cách động bằng cách sử dụng mô hình Decorator.

Đầu tiên, chúng ta định nghĩa một lớp cơ sở trừu tượng CarFeature mà tất cả các decorator sẽ kế thừa từ đó:

abstract class CarFeature extends Car {
  protected car: Car;

  constructor(car: Car) {
    super(car.getMake(), car.getModel(), car.getYear(), car.getPrice());
    this.car = car;
  }

  public abstract getPrice(): number;
}

Tiếp theo, chúng ta triển khai các concrete decorator class để thêm các freatures mong muốn cho lớp car:

class SalesTaxDecorator extends CarFeature {
  public getPrice(): number {
    return this.car.getPrice() * 1.10; // 10% sales tax
  }
}

class NavigationDecorator extends CarFeature {
  public getPrice(): number {
    return this.car.getPrice() + 1500; // add $1500 for navigation system
  }
}

class SunroofDecorator extends CarFeature {
  public getPrice(): number {
    return this.car.getPrice() + 1000; // add $1000 for sunroof
  }
}

Sau đó, chúng ta dùng decorators này để thêm functionally vào lớp car 1 cách dynamically:

// Create a basic car
const car = new Car("Honda", "Accord", 2022, 25000);

// Add sales tax to the car
const carWithSalesTax = new SalesTaxDecorator(car);

// Add a navigation system to the car
const carWithNavigation = new NavigationDecorator(carWithSalesTax);

// Add a sunroof to the car
const carWithSunroof = new SunroofDecorator(carWithNavigation);

console.log(`Make: ${carWithSunroof.getMake()}`);
console.log(`Model: ${carWithSunroof.getModel()}`);
console.log(`Year: ${carWithSunroof.getYear()}`);
console.log(`Price: ${carWithSunroof.getPrice()}`);

// Output:
// Make: Honda
// Model: Accord
// Year: 2022
// Price: 28750

Trong ví dụ này, chúng ta sử dụng mô hình Decorator để thêm chức năng vào một đối tượng Car một cách động. Mỗi decorator kế thừa từ lớp trừu tượng CarFeature và thêm một số chức năng bổ sung vào đối tượng Car.

Chúng ta có thể thêm nhiều decorator vào một đối tượng Car theo bất kỳ thứ tự nào và mỗi decorator đều thêm chức năng riêng của nó vào đối tượng. Điều này cho phép chúng ta tạo ra các đối tượng Car được tùy chỉnh cao với chỉ các tính năng chúng ta cần, trong khi vẫn giữ cho lớp Car cốt lõi đơn giản và dễ bảo trì.

Last updated