Thiết kế hệ thống ứng dụng: MVC, IoC/DI, Metadata và Decorator trong TypeScript

Mô hình kiến trúc MVC

MVC (Model-View-Controller) là một mẫu thiết kế phổ biến giúp phân tách logic ứng dụng thành ba thành phần chính:
  • Model (Mô hình): Quản lý dữ liệu và quy tắc nghiệp vụ. Đại diện cho trạng thái và hành vi của ứng dụng, tương tác trực tiếp với cơ sở dữ liệu hoặc nguồn dữ liệu khác.
  • View (Giao diện): Trình bày dữ liệu từ Model đến người dùng dưới dạng giao diện đồ họa, chẳng hạn như HTML, UI components, hoặc báo cáo.
  • Controller (Bộ điều khiển): Nhận đầu vào từ người dùng qua View, xử lý yêu cầu, cập nhật Model nếu cần, rồi điều hướng việc cập nhật View.
Luồng hoạt động: 1. Người dùng thao tác trên View. 2. Controller nhận thông tin từ View. 3. Controller gọi phương thức trong Model để xử lý dữ liệu. 4. Model cập nhật trạng thái hoặc truy xuất dữ liệu. 5. Controller gửi lại dữ liệu đã cập nhật cho View để hiển thị. Đây là một cách hiệu quả để tách biệt các khía cạnh chức năng, hỗ trợ tái sử dụng, dễ bảo trì và mở rộng.

IoC và DI – Kiến trúc quản lý phụ thuộc

Control Inversion (IoC) là nguyên lý thiết kế chuyển quyền kiểm soát việc tạo đối tượng và quản lý phụ thuộc từ mã ứng dụng sang một hệ thống bên ngoài (thường là container). Dependency Injection (DI) là một kỹ thuật cụ thể để thực hiện IoC, nơi các phụ thuộc được cung cấp từ bên ngoài vào lớp chứ không tự tạo.
// Ví dụ: Inject dependency qua constructor
class DatabaseRepository {
    getData() {
        return "Dữ liệu từ CSDL";
    }
}

class UserService {
    private repo: DatabaseRepository;

    constructor(repo: DatabaseRepository) {
        this.repo = repo;
    }

    getProfile() {
        return this.repo.getData();
    }
}

// Tạo instance và inject
const repo = new DatabaseRepository();
const service = new UserService(repo);

console.log(service.getProfile()); // Dữ liệu từ CSDL
Các hình thức inject bao gồm: - **Constructor injection**: Truyền phụ thuộc qua hàm khởi tạo. - **Property injection**: Gán phụ thuộc qua setter. - **Method injection**: Gửi phụ thuộc qua phương thức cụ thể.

Metadata – Dữ liệu về dữ liệu

Metadata là thông tin mô tả cấu trúc, ngữ nghĩa hoặc đặc điểm của dữ liệu. Trong TypeScript, metadata có thể được thu thập bằng `Reflect` API và thư viện `reflect-metadata`. Ví dụ sử dụng để lưu trữ loại tham số, kiểu trả về, hay định nghĩa hành vi tại thời điểm chạy:
import "reflect-metadata";

function LogExecution(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    const original = descriptor.value;
    descriptor.value = function (...args: any[]) {
        console.log(`Bắt đầu gọi ${propertyKey}`);
        const result = original.apply(this, args);
        console.log(`Kết thúc gọi ${propertyKey}`);
        return result;
    };
}

class Calculator {
    @LogExecution
    add(a: number, b: number): number {
        return a + b;
    }
}

const calc = new Calculator();
calc.add(5, 3); // In ra: Bắt đầu gọi add, Kết thúc gọi add
Các key metadata thường gặp: - `design:paramtypes`: Mảng kiểu tham số. - `design:returntype`: Kiểu giá trị trả về. - `design:type`: Kiểu của thuộc tính hoặc phương thức.

Phản chiếu (Reflection) trên tuple

Trong TypeScript, phản chiếu cho phép truy xuất thông tin về cấu trúc dữ liệu tại thời điểm chạy. Với tuple, ta có thể sử dụng `Reflect` để thao tác:
const dataTuple = [100, "test", true];

// Truy xuất phần tử
const first = Reflect.get(dataTuple, 0);
console.log(first); // 100

// Cập nhật phần tử
Reflect.set(dataTuple, 1, "updated");
console.log(dataTuple); // [100, "updated", true]

// Xóa phần tử
Reflect.deleteProperty(dataTuple, 2);
console.log(dataTuple); // [100, "updated"]

Decorator – Mở rộng chức năng mà không sửa đổi code gốc

Decorator là một mẫu thiết kế cho phép thêm chức năng vào đối tượng mà không thay đổi cấu trúc của nó. Trong TypeScript, decorator được áp dụng như một cú pháp đặc biệt cho các lớp, phương thức, thuộc tính. Yêu cầu cấu hình trong `tsconfig.json`:
{
  "compilerOptions": {
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true,
    "target": "ES2020"
  }
}
Các loại decorator phổ biến:
  • Class decorator: Thêm hành vi toàn bộ lớp.
  • Method decorator: Bao bọc phương thức để mở rộng chức năng.
  • Property decorator: Điều chỉnh thuộc tính (ví dụ: đọc chỉ, viết chặn).
  • Parameter decorator: Ghi nhận thông tin về tham số.
  • Accessor decorator: Theo dõi truy cập vào getter/setter.
Ví dụ: Giới hạn gán giá trị cho thuộc tính:
function ReadOnly(target: any, propertyName: string) {
    let value: any;
    Object.defineProperty(target, propertyName, {
        get: () => value,
        set: (newValue) => {
            console.warn(`Không thể thay đổi thuộc tính ${propertyName}`);
        },
        enumerable: true,
        configurable: true
    });
}

class UserProfile {
    @ReadOnly
    username: string;

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

const user = new UserProfile("admin");
console.log(user.username); // admin
user.username = "guest"; // Cảnh báo: Không thể thay đổi...

Thẻ: typescript MVC Dependency Injection ioc decorator

Đăng vào ngày 19 tháng 9 lúc 22:48