+3

Decorator trong TypeScript

Trong bài viết này, chúng ta sẽ tìm hiểu về Decorator trong TypeScript, một tính năng mạnh mẽ giúp mở rộng và thay đổi hành vi của các class, method, property và parameter. Decorator giúp làm cho code trở nên dễ đọc và dễ bảo trì hơn. Hãy cùng khám phá cách sử dụng và những lợi ích mà Decorator mang lại.

1. Decorator là gì?

Decorator là một function đặc biệt được dùng để thay đổi hoặc mở rộng hành vi của class, method, property hoặc parameter trong TypeScript. Decorator cho phép chúng ta thêm chức năng mới mà không cần sửa đổi code ban đầu của đối tượng.

2. Decorator và Lập trình hướng khía cạnh (AOP)

Decorator trong TypeScript có mối liên hệ nhất định với lập trình hướng khía cạnh (Aspect-Oriented Programming - AOP). AOP là một mô hình lập trình nhằm mục đích tách biệt các mối quan tâm xuyên suốt (cross-cutting concerns) khỏi logic chính của ứng dụng. Những mối quan tâm này thường bao gồm logging, bảo mật, xử lý ngoại lệ, và các nhiệm vụ phụ trợ khác.

Trong AOP, có một số khái niệm chính như Aspect (module chứa logic phụ trợ), Join Point (điểm trong quá trình thực thi nơi một aspect có thể áp dụng), Advice (đoạn code được thực thi tại một join point) và Pointcut (biểu thức định nghĩa join points mà một aspect sẽ áp dụng). Decorator trong TypeScript có thể được coi là một biểu hiện cụ thể của các khái niệm này, khi chúng cho phép chúng ta thêm hoặc thay đổi hành vi của các class, method, property hoặc parameter mà không cần sửa đổi code ban đầu.

3. Decorator và Decorator Design Pattern

Decorator trong TypeScript cũng liên quan đến Decorator Design Pattern. Cả hai đều được sử dụng để thay đổi hoặc mở rộng hành vi của đối tượng, nhưng được áp dụng trong các ngữ cảnh khác nhau.

Decorator Design Pattern là một design pattern cho phép thêm hành vi cho các đối tượng mà không ảnh hưởng đến các đối tượng khác cùng class. Design pattern này thường được triển khai bằng composition.

Ví dụ về Decorator Design Pattern:

interface Component {
    operation(): string;
}

class ConcreteComponent implements Component {
    operation(): string {
        return 'ConcreteComponent';
    }
}

class Decorator implements Component {
    protected component: Component;

    constructor(component: Component) {
        this.component = component;
    }

    operation(): string {
        return this.component.operation();
    }
}

class ConcreteDecoratorA extends Decorator {
    operation(): string {
        return `ConcreteDecoratorA(${super.operation()})`;
    }
}

class ConcreteDecoratorB extends Decorator {
    operation(): string {
        return `ConcreteDecoratorB(${super.operation()})`;
    }
}

const simple = new ConcreteComponent();
console.log(simple.operation());

const decoratedA = new ConcreteDecoratorA(simple);
console.log(decoratedA.operation());

const decoratedB = new ConcreteDecoratorB(decoratedA);
console.log(decoratedB.operation());

TypeScript Decorators là một tính năng ngôn ngữ cho phép thêm annotation và metadata vào các class và members. Chúng được sử dụng để thay đổi hành vi của các class, method, property hoặc parameter.

Ví dụ về Decorator trong TypeScript:

function sealed(constructor: Function) {
    Object.seal(constructor);
    Object.seal(constructor.prototype);
}

@sealed
class Greeter {
    greeting: string;

    constructor(message: string) {
        this.greeting = message;
    }

    greet() {
        return `Hello, ${this.greeting}`;
    }
}

4. Cách khai báo Decorator

Trong TypeScript, Decorator được khai báo bằng cách sử dụng ký hiệu @ theo sau là tên của hàm decorator. Có bốn loại decorator chính:

  1. Class Decorator: Áp dụng cho các class.
  2. Method Decorator: Áp dụng cho các method của class.
  3. Property Decorator: Áp dụng cho các property của class.
  4. Parameter Decorator: Áp dụng cho các parameter của method trong class.

5. Ví dụ về Decorator

Class Decorator

Class Decorator được sử dụng để thêm hoặc thay đổi hành vi của một class. Dưới đây là một ví dụ về Class Decorator:

function sealed(constructor: Function) {
    Object.seal(constructor);
    Object.seal(constructor.prototype);
}

@sealed
class Greeter {
    greeting: string;

    constructor(message: string) {
        this.greeting = message;
    }

    greet() {
        return `Hello, ${this.greeting}`;
    }
}

Trong ví dụ trên, decorator @sealed sẽ khóa class Greeter và ngăn không cho thêm hoặc xóa các property và method của nó.

Method Decorator

Method Decorator được sử dụng để thay đổi hành vi của một method. Dưới đây là ví dụ về Method Decorator:

function enumerable(value: boolean) {
    return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
        descriptor.enumerable = value;
    };
}

class Greeter {
    greeting: string;

    constructor(message: string) {
        this.greeting = message;
    }

    @enumerable(false)
    greet() {
        return `Hello, ${this.greeting}`;
    }
}

Trong ví dụ trên, decorator @enumerable(false) sẽ làm cho method greet không liệt kê được (non-enumerable).

Property Decorator

Property Decorator được sử dụng để thay đổi hành vi của một property. Dưới đây là ví dụ về Property Decorator:

function logProperty(target: any, key: string) {
    let _val = target[key];

    const getter = () => {
        console.log(`Get: ${key} => ${_val}`);
        return _val;
    };

    const setter = (newVal) => {
        console.log(`Set: ${key} => ${newVal}`);
        _val = newVal;
    };

    Object.defineProperty(target, key, {
        get: getter,
        set: setter,
        enumerable: true,
        configurable: true
    });
}

class Greeter {
    @logProperty
    greeting: string;

    constructor(message: string) {
        this.greeting = message;
    }

    greet() {
        return `Hello, ${this.greeting}`;
    }
}

Trong ví dụ trên, decorator @logProperty sẽ ghi log mỗi khi property greeting được truy cập hoặc thay đổi.

Parameter Decorator

Parameter Decorator được sử dụng để thay đổi hành vi của một parameter trong method. Dưới đây là ví dụ về Parameter Decorator:

function logParameter(target: any, key: string, index: number) {
    const metadataKey = `__log_${key}_parameters`;

    if (Array.isArray(target[metadataKey])) {
        target[metadataKey].push(index);
    } else {
        target[metadataKey] = [index];
    }
}

class Greeter {
    greeting: string;

    constructor(message: string) {
        this.greeting = message;
    }

    greet(@logParameter message: string) {
        return `Hello, ${message}`;
    }
}

Trong ví dụ trên, decorator @logParameter sẽ ghi log chỉ số của parameter khi method greet được gọi.

6. Ứng dụng của Decorator trong TypeScript

Decorator trong TypeScript đã được sử dụng rộng rãi trong nhiều framework và thư viện nổi tiếng như Angular, NestJS, TypeORM, và nhiều công cụ khác.

  • Angular: Sử dụng decorators để định nghĩa các component, service và các thành phần khác của app.

    @Component({
      selector: 'app-root',
      templateUrl: './app.component.html',
      styleUrls: ['./app.component.css']
    })
    export class AppComponent {
      title = 'my-app';
    }
    
  • NestJS: Sử dụng decorators để định nghĩa các controller, service và module trong ứng dụng.

    @Controller('cats')
    export class CatsController {
      @Get()
      findAll(): string {
        return 'This action returns all cats';
      }
    }
    
  • TypeORM: Sử dụng decorators để định nghĩa các entity và mối quan hệ (relationship) giữa chúng.

    @Entity()
    export class User {
      @PrimaryGeneratedColumn()
      id: number;
    
      @Column()
      firstName: string;
    
      @Column()
      lastName: string;
    }
    

7. Kết luận

Decorators trong TypeScript là một công cụ mạnh mẽ giúp mở rộng và thay đổi hành vi của class, method, property và parameter mà không cần phải sửa đổi code ban đầu. Việc sử dụng decorators giúp cho code trở nên rõ ràng hơn và dễ bảo trì hơn. Thêm vào đó, chúng có liên quan mật thiết đến các khái niệm trong lập trình hướng khía cạnh (AOP) và Decorator Design Pattern, giúp tách biệt các mối quan tâm xuyên suốt ra khỏi logic chính của ứng dụng. Hy vọng qua bài viết này, bạn đã hiểu thêm về decorators và cách sử dụng chúng trong TypeScript.


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí