openplanning

Phương thức trong TypeScript

  1. Phương thức là gì?
  2. Phương thức thông thường
  3. Phương thức tĩnh
  4. Phương thức trừu tượng
  5. Các tham số tuỳ chọn
  6. Các tham số với giá trị mặc định
  7. Các tham số với kiểu dữ liệu liên hợp
  8. Ghi đè phương thức
  9. Method Overloading

1. Phương thức là gì?

Trong ngôn ngữ lập trình TypeScript, phương thức là một khối mã (block of code) được định nghĩa bên trong một lớp và chỉ chạy khi được gọi. Các phương thức chia nhiệm vụ lớn thành các phần nhỏ và thực hiện hoạt động cụ thể của chương trình đó. Quá trình này làm tăng khả năng tái sử dụng mã và nâng cao cách tiếp cận mô-đun của chương trình.
Về cơ bản, phương thức được chia làm 3 loại:
  • Phương thức thông thường (không tĩnh và không trừu tượng).
  • Phương thức tĩnh (static method).
  • Phương thức trừu tượng (abstract method).

2. Phương thức thông thường

Cú pháp định nghĩa một phương thức thông thường:
[private | protected | public] method_name(arguments) : return_type {  
     // statement(s)  
}
  • return_type: Kiểu dữ liệu trả về của phương thức. Sử dụng từ khoá void như một kiểu trả về nếu phương thức không trả về gì cả.
  • method_name: Tên phương thức. Khác với các ngôn ngữ khác như Java, TypeScript không cho phép 2 phương thức cùng tên, kể cả khi chúng có các tham số khác nhau. Điều này là cần thiết để có thể chuyển đổi mã TypeScript sang Javascript.
  • arguments: Các tham số của phương thức.
Ví dụ: Lớp Cat và phương thức sayHello(..) của nó. Để gọi phương thức sayHello(..) bạn phải tạo một đối tượng Cat và gọi phương thức bằng cách sử dụng ký hiệu dấu chấm (dot notation).
method_ex1.ts
class Cat {
    sayHello(name: string): void {
        console.log(`Hello ${name}`);
    }
}
function method_ex1_test() {
    var tom: Cat = new Cat(); // Create a Cat object.
    tom.sayHello("Jerry"); // Call the method through the object.
}
// Call the method:
method_ex1_test();
Output:
Hello Jerry
Một phương thức có thể chứa 0, 1 hoặc nhiều tham số, các tham số cách nhau bởi dấu phẩy.
method_ex2.ts
class Rectangle {
    width: number;
    height: number;

    constructor(width: number, height: number) {
        this.width = width;
        this.height = height;
    }
    getArea(): number {
        return this.width * this.height;
    }
    changeWidthHeight(newWidth: number, newHeight: number): void {
        this.width = newWidth;
        this.height = newHeight;
    }
    showMe(): void {
        console.log(`I am a rectangle, width: ${this.width}, height: ${this.height}`);
    }
}
function method_ex2_test() {
    var rec = new Rectangle(5, 10); // Create an object.

    rec.showMe();  // Call the method.
    var area = rec.getArea();  // Call the method.
    console.log(`Area: ${area}`);

    console.log(` --- Change width and height --- `);
    rec.changeWidthHeight(25, 15); // Set newWidth, newHeight

    rec.showMe(); // Call the method.  
    area = rec.getArea(); // Call the method.
    console.log(`Area: ${area}`);
}
method_ex2_test(); // Call the function.
Output:
I am a rectangle, width: 5, height: 10
Area: 50
 --- Change width and height ---
I am a rectangle, width: 25, height: 15
Area: 375

3. Phương thức tĩnh

TypeScript sử dụng từ khoá static kết hợp với cú pháp định nghĩa một phương thức thông thường để định nghĩa một phương thức tĩnh.
Cú pháp:
[private | protected | public] static method_name(arguments) : return_type {  
     // statement(s)  
}
Các đặc điểm của phương thức tĩnh:
  • Phương thức tĩnh được gọi thông qua tên lớp và ký hiệu dấu chấm. Chẳng hạn MyUtility.sum(100, 50).
  • Các thành viên không tĩnh của một lớp không thể xuất hiện trong phương thức tĩnh trừ khi chúng được truy cập thông qua đối tượng (Xem thêm ví dụ phía dưới).
method_static_ex1.ts
class MyUtility {
    static sum(a: number, b: number): number { // same as 'public'
        return a + b;
    }
    public static minus(a: number, b: number): number {
        return a - b;
    }
}
function method_static_ex1_test() {
    var result = MyUtility.sum(100, 50);
    console.log(`Sum Result: ${result}`);

    result = MyUtility.minus(100, 50);
    console.log(`Minus Result: ${result}`);
}
method_static_ex1_test(); // Call the method
Output:
Sum Result: 150
Minus Result: 50
Tiếp theo, nhìn vào ví dụ dưới đây:
  • side1, side2side3 là các trường (field) không tĩnh của lớp Triangle, chúng không thể xuất hiện trong phương thức tĩnh.
method_static_ex2.ts
class Triangle {
    side1: number; side2: number; side3: number;// Non-static fields (Cann't use in static method)
    static DEFAULT_COLOR:string = 'blue'; // Static field (Can use in static method)

    constructor(s1: number, s2: number, s3: number) {
        var valid = Triangle.isValidSides(s1, s2, s3); // Check if all sides are valid.
        if (!valid) {
            throw new Error('Invalid Sides!'); // Throw an Error.
        }
        this.side1 = s1;
        this.side2 = s2;
        this.side3 = s3;
    }
    static isValidSides(s1: number, s2: number, s3: number): boolean {
        if (s1 < 0 || s2 < 0 || s3 < 0) {
            return false;
        }
        return s1 + s2 > s3 && s1 + s3 > s2 && s2 + s3 > s1;
    }
    isEquilateralTriangle(): boolean {
        return this.side1 == this.side2 && this.side2 == this.side3;
    }
}
function method_static_ex2_test() {
    var valid = Triangle.isValidSides(6, 8, 10);
    console.log(`Are sides 6, 8 and 10 valid to make a triangle? ${valid}`);

    var triangle = new Triangle(3.0, 4.0, 5.0);
    // Check if the triangle is equilateral triangle.
    var check = triangle.isEquilateralTriangle();
    console.log(`Is Equilateral Triangle? ${check}`);
}
method_static_ex2_test(); // Call the method.
Output:
Are sides 6, 8 and 10 valid to make a triangle? true
Is Equilateral Triangle? false

4. Phương thức trừu tượng

Trong ngôn ngữ TypeScript, phương thức trừu tượng là một phương thức không tĩnh và không có nội dung.
[protected | public] abstract method_name(arguments) : return_type;
Một lớp có ít nhất một phương thức trừu tượng phải được khai báo là trừu tượng, và một trong các lớp con của nó phải ghi đè (override) các phương thức trừu tượng này và viết nội dung cho chúng.
method_abstract_ex1.ts
abstract class Person {
    abstract sayHello(): void; // An abstract method.
}
class EnglishPerson extends Person {
    sayHello(): void {
        console.log("Hello");
    }
}
class RussianPerson extends Person {
    sayHello(): void {
        console.log("Привет");
    }
}
function method_abstract_ex1_test() {
    var enPerson = new EnglishPerson();
    enPerson.sayHello();

    var ruPerson = new RussianPerson();
    ruPerson.sayHello();
}
method_abstract_ex1_test(); // Call the function.
Output:
Hello
Привет
  • TypeScript Classes

5. Các tham số tuỳ chọn

Như đã đề cập ở trên, các lớp trong TypeScriptJavaScript đều không cho phép phương thức trùng tên, nhưng một phương thức có thể bao gồm các tham số tuỳ chọn.
Cú pháp:
// Non-static method with optional arguments:
[private | protected | public] method_name(args, optional_args?): return_type {  
     // statement(s)  
}

// Static method with optional arguments:
[private | protected | public] static method_name(args, optional_args?) : return_type {  
     // statement(s)  
}

// Abstract method with optional arguments:
[protected | public] abstract method_name(args, optional_args?): return_type;
Ví dụ:
method_optional_args_ex1.js
class MyUtils {
    static concat(s1: string, s2: string, s3?: string): string {
        if (s3) {
            return s1 + s2 + s3;
        }
        return s1 + s2;
    }
    static sum(v1: number, v2: number, v3?: number, v4?: number): number {
        return v1 + v2 + (v3 ?? 0) + (v4 ?? 0);
    }
}
function method_optional_args_ex1_test() {
    var result1 = MyUtils.concat('One', 'Two');
    console.log(`result1: ${result1}`);

    var result2 = MyUtils.concat('One', 'Two', 'Three');
    console.log(`result2: ${result2}`);

    var value1 = MyUtils.sum(1, 2, 3, 4);
    console.log(`value1: ${value1}`);

    var value2 = MyUtils.sum(1, 2, 3);
    console.log(`value2: ${value2}`);

    var value3 = MyUtils.sum(1, 2);
    console.log(`value3: ${value3}`);
}
method_optional_args_ex1_test(); // Call the method.
Output:
result1: OneTwo
result2: OneTwoThree
value1: 10
value2: 6
value3: 3

6. Các tham số với giá trị mặc định

TypeScript hỗ trợ phương thức với các tham số có giá trị mặc định, các tham số này phải là các tham số cuối cùng trong danh sách các tham số.
Cú pháp:
// Non-static method
[private | protected | public ] method_name (
             arg1 : data_type1, arg2 : data_type2,
             arg3 : data_type3 = defaultValue3,
             arg4 : data_type4 = defaultValue4) : return_type {
    // statement(s)  
}
// Static method
[private | protected | public ] static method_name (
             arg1 : data_type1, arg2 : data_type2,
             arg3 : data_type3 = defaultValue3,
             arg4 : data_type4 = defaultValue4) : return_type {
   // statement(s)  
}
// Abstract method
[protected | public ] abstract method_name (
             arg1 : data_type1, arg2 : data_type2,
             arg3 : data_type3 = defaultValue3,
             arg4 : data_type4 = defaultValue4) : return_type;
Ví dụ:
method_default_value_args_ex1.ts
class SalesUtils {
    static applyDiscount(amount : number, discount: number = 0.05)  : number {
        return amount * (1 - discount);
    }
}
function method_default_value_args_ex1_test() {
    var value1 = SalesUtils.applyDiscount(1000);
    console.log(`value1 = ${value1}`);

    var value2 = SalesUtils.applyDiscount(1000, 0.5);
    console.log(`value2 = ${value2}`);
}
method_default_value_args_ex1_test(); // Call the method.
Output:
value1 = 950
value2 = 500

7. Các tham số với kiểu dữ liệu liên hợp

Tham số trong phương thức cũng có thể được khai báo với kiểu dữ liệu liên hợp (union data type).
// Non-static method
[private | protected | public ] method_name (
             arg1 : data_type1, arg2 : data_type2,
             arg3 : data_type31 | data_type32 | data_type3N, // Union Data Type
             arg4 : data_type4 = defaultValue4) : return_type {
    //  Statament(s)
}
// Static method
[private | protected | public ] static method_name (
             arg1 : data_type1, arg2 : data_type2,
             arg3 : data_type31 | data_type32 | data_type3N, // Union Data Type
             arg4 : data_type4) : return_type {
    //  Statament(s)  
}
// Abstract method
[protected | public ] abstract method_name (
             arg1 : data_type1, arg2 : data_type2,
             arg3 : data_type31 | data_type32 | data_type3N, // Union Data Type
             arg4 : data_type4) : return_type;
Ví dụ:
method_union_type_args_ex1.ts
interface IStudent {
    studentId: number,
    studentName: string
}
interface IWorker {
    workerId: number,
    workerName: string
}
class AppUtils {
    static getName(person: IStudent | IWorker): string { // Union Type Arg
        var p = person as IStudent;
        if (p.studentName) {
            return p.studentName;
        }
        return (person as IWorker).workerName;
    }
}
function method_union_type_args_ex1_test() {
    var student = { studentId: 1, studentName: "Tom" };
    var worker = { workerId: 2, workerName: "Jerry" };

    var name1 = AppUtils.getName(student);
    var name2 = AppUtils.getName(worker);
    console.log(`name1: ${name1}`);
    console.log(`name2: ${name2}`);
}
method_union_type_args_ex1_test(); // Call the method.
Output:
name1: Tom
name2: Jerry

8. Ghi đè phương thức

Lớp con có thể ghi đè (override) một phương thức nào đó của lớp cha nếu các điều kiện sau được thoả mãn:
  • Hai phương thức phải cùng tên và có các tham số giống nhau.
  • Kiểu trả về của 2 phương thức phải giống nhau, hoặc kiểu trả về của phương thức tại lớp con phải là kiểu con của kiểu trả về của phương thức tại lớp cha.
Ví dụ:
  • Lớp Mouse ghi đè (override) phương thức sayAnything() của lớp Animal.
method_override_ex1.ts
class Animal {
    sayAnything(): void {
        console.log('<Nothing>');
    }
}
class Mouse extends Animal {
    sayAnything(): void { // Override method of parent class.
        console.log('Hi Tom!');
    }
}
function method_override_ex1_test() {
    var animal = new Mouse();
    animal.sayAnything(); // Hi Tom!
}
method_override_ex1_test(); // Call the function.
Output:
Hi Tom!
Ví dụ: Bạn cũng có thể sử dụng từ khoá super để gọi tới phương thức cùng tên của lớp cha.
method_override_ex2.ts
class BoldFormatter {
    formatText(text: string): string {
        return '<b>' + text + '</b>';
    }
}
class BoldItalicFormatter extends BoldFormatter {
    formatText(text: string): string { // Override method of parent class.
        var boldText = super.formatText(text); // Call super method.
        return '<i>' + boldText + '</i>';
    }
}
function method_override_ex2_test() {
    var formatter = new BoldItalicFormatter();
    var formattedText = formatter.formatText('Hello');
    console.log(formattedText);
}
method_override_ex2_test(); // Call the method.
Output:
<i><b>Hello</b></i>

9. Method Overloading

Trong bài viết về các hàm trong TypeScript, tôi đã giới thiệu về "Function Overloading" (Nạp chồng hàm). Phương thức được coi là hàm của lớp vì vậy nó cũng có khái niệm tương tự đó là "Method Overloading" (Nạp chồng phương thức).
TypeScript không cho phép 2 phương thức cùng tên trong một lớp kể cả trường hợp chúng có các tham số khác nhau. Method Overloading cho phép bạn định nghĩa một phương thức với nhiều kiểu tham số khác nhau.
Trong TypeScript, Method Overloading trông khác với trong C++, Java hoặc C#. Ý tưởng chính để nạp chồng (overload) cho phương thức là tạo một phương thức chung để kiểm tra loại tham số nào đã được truyền vào khi phương thức được gọi, và sau đó thực hiện một số logic cho trường hợp thích hợp. Hữu ích là thêm các định nghĩa cho phương thức để giúp các lập trình viên khác biết cách sử dụng nó một cách thích hợp.
Cú pháp:
// Definition 1
[static] method_name(arg_11 : type_11, arg_1N : type_1N): return_type;
]// Definition 2
[static] method_name(arg_21 : type_21, arg_22 : type_22, arg_2M : type_2M) : return_type;
[static] method_name(... args : any[]) : return_type {
   // Method body.
}
Ví dụ:
method_overloading_ex1.ts
interface IDimension {
    width: number,
    height: number
}
class MathUtils {
    static getArea(dimension: IDimension): number; // Definition 1
    static getArea(width: number): number; // Definition 2
    static getArea(width: number, height: number): number; // Definition 3
    static getArea(...args: any[]): number {
        if (args.length == 1) {
            if (typeof args[0] == 'number') { // Use Definition 2
                return args[0] * args[0];
            } else { // Use Definition 1
                var dim = args[0] as IDimension;
                return dim.width * dim.height;
            }
        } else if (args.length == 2) {
            return args[0] * args[1];
        } else {
            throw new Error('Arguments Invalid!');
        }
    }
}
function method_overloading_ex1_test() {
    var area1 = MathUtils.getArea({ width: 10, height: 20 }); // 200
    var area2 = MathUtils.getArea(10); // 100
    var area3 = MathUtils.getArea(10, 15); // 150

    console.log(`area1: ${area1}`);
    console.log(`area2: ${area2}`);
    console.log(`area3: ${area3}`);
}
method_overloading_ex1_test(); // Call the function.
Output:
area1: 200
area2: 100
area3: 150