Class

Trong TypeScript, class là một cách để xác định kiểu đối tượng. Nó giúp chúng ta tổ chức và quản lý mã trong một cấu trúc phân cấp, dễ dàng bảo trì và mở rộng ứng dụng.

Một class trong TypeScript bao gồm các thành phần như thuộc tính (properties), phương thức (methods) , hàm khởi tạo (constructor). Chúng ta có thể sử dụng từ khóa "class" để tạo một class trong TypeScript.

class Employee { empCode: number; empName: string; constructor(code: number, name: string){ this.empCode = code; this.empName = name; } getSalary() : number{ return 10000; } }

Constructor

Hàm khởi tạo là một loại phương thức đặc biệt được gọi khi tạo một đối tượng. Trong TypeScript, phương thức khởi tạo luôn được định nghĩa với tên "constructor".

class Employee { empCode: number; empName: string; constructor(code: number, name: string){ this.empCode = code; this.empName = name; } }

Trong ví dụ trên, lớp Employee bao gồm một constructor với các tham số codename. Trong constructor, các thành viên của lớp có thể được truy cập bằng từ khóa this, ví dụ: this.empCode hoặc this.empName.

Creating an object of class

Một đối tượng của lớp có thể được tạo bằng từ khóa new.

class Employee { empCode: number; empName: string; constructor(code: number, name: string){ this.empCode = code; this.empName = name; } } let emp = new Employee(100, "Steve"); console.log(emp.empCode); //100 console.log(emp.empName); //Steve

Trong ví dụ trên, chúng ta truyền giá trị vào đối tượng để khởi tạo các biến thành viên. Khi chúng ta khởi tạo một đối tượng mới, constructor của lớp được gọi với các giá trị được truyền và các biến thành viên empCode và empName được khởi tạo với các giá trị này.

Inheritance

Giống như các ngôn ngữ hướng đối tượng khác như Java và C#, trong TypeScript, các lớp có thể được mở rộng để tạo ra các lớp mới với tính kế thừa, bằng cách sử dụng từ khóa extends.

class Person { name: string; gender: string; constructor(name: string, gender: string) { this.name = name; this.gender = gender; } } class Employee extends Person { empCode: number; constructor(empCode: number, name: string, gender: string){ super(name, gender); this.empCode = empCode; } displayName():void { console.log("Name = " + this.name + ", Employee Code = " + this.empCode) } } let emp = new Employee(100, "Bill", "Male"); emp.displayName(); //"Name = Bill, Employee Code = 100"

Trong ví dụ trên, lớp Employee mở rộng lớp Person bằng cách sử dụng từ khóa extends. Điều này có nghĩa là lớp Employee bây giờ bao gồm tất cả các thành viên của lớp Person.

Hàm khởi tạo của lớp Employee khởi tạo các thành viên của nó cũng như các thuộc tính của lớp cha bằng cách sử dụng từ khóa đặc biệt 'super'. Từ khóa super được sử dụng để gọi hàm khởi tạo của lớp cha và chuyển các giá trị thuộc tính.

Lưu ý: Chúng ta phải gọi phương thức super() trước khi gán giá trị cho các thuộc tính trong constructor của lớp con.

Class implements interface

Một class có thể thực thi một hoặc nhiều interface

interface IPerson { name: string; display(): void; } interface IEmployee { empCode: number; } class Employee implements IPerson,IEmployee { empCode: number; name: string; constructor(empCode: number, name: string){ this.empCode = empCode; this.name = name; } display(): void{ console.log("Name = " + this.name + ", Employee Code = " + this.empCode) } } let per:IPerson = new Employee(100, "Bill"); per.display(); let emp:IEmployee = new Employee(100, "Bill"); emp.display(); //Compile Error: Property 'display' does not exist on type 'IEmployee'.

Trong ví dụ trên, lớp Employee triển khai hai interface - IPerson và IEmployee. Vì vậy, một thể hiện của lớp Employee có thể được gán cho một biến có kiểu IPerson hoặc IEmployee. Tuy nhiên, một đối tượng kiểu IEmployee không thể gọi phương thức display() vì IEmployee không bao gồm nó. Bạn chỉ có thể sử dụng các thuộc tính và phương thức được định nghĩa riêng cho kiểu đối tượng đó.

Interface extends class

class Person { name: string; constructor(name: string){ this.name = name } } interface IEmployee extends Person { empCode: number; } let emp: IEmployee = { empCode: 1, name: "James" }

Method overriding

Khi một lớp con định nghĩa cài đặt của riêng mình cho một phương thức từ lớp cha, điều này được gọi là ghi đè phương thức (method overriding).

class Car { name: string; constructor(name: string){ this.name = name; } run(speed: number = 0){ console.log("A " + this.name + " is moving at" + speed + " mph!"); } } class Mercedes extends Car{ constructor(name: string){ super(name) } run(speed = 150){ console.log('A Mercedes started') super.run(speed) } } class Honda extends Car { constructor(name:string){ super(name) } run(speed = 100){ console.log('A Honda started') super.run(speed) } } let mercObj = new Mercedes("Mercedes-Benz GLA"); let hondaObj = new Honda("Honda City"); mercObj.run(); // "A Mercedes started" // "A Mercedes-Benz GLA is moving at150 mph!" hondaObj.run(); // "A Honda started" // "A Honda City is moving at100 mph!"

Trong ví dụ trên, chúng ta có một lớp Car với thuộc tính name. Constructor cho lớp này khởi tạo các biến thành viên. Lớp cũng có một phương thức run() với đối số speed được khởi tạo là 0.

Sau đó, chúng ta tạo hai lớp con, Mercedes và Honda, kế thừa từ lớp cha Car. Mỗi lớp con kế thừa các thuộc tính của lớp cha. Constructor cho mỗi lớp gọi constructor của lớp cha để khởi tạo các thuộc tính của lớp cha. Mỗi lớp cũng định nghĩa một phương thức run() in ra thông điệp riêng của nó ngoài việc gọi phương thức của lớp cha cho run().

Vì mỗi lớp con có sự triển khai riêng của phương thức run() của lớp cha, điều này được gọi là phương thức ghi đè, tức là các lớp con có một phương thức cùng tên như phương thức của lớp cha.

Khi chúng ta tạo các đối tượng của lớp con và gọi phương thức run() trên đối tượng này, nó sẽ gọi phương thức run() của riêng nó và không phải là của lớp cha.

Abstract class

Trong TypeScript, một abstract class cũng là một lớp mà không thể khởi tạo trực tiếp, và được sử dụng như một bản thiết kế cho các lớp con. Một abstract class có thể chứa các phương thức abstract (phương thức chỉ khai báo mà không có thân hàm) hoặc các phương thức cụ thể (đã được triển khai), và các thuộc tính.

Khi một lớp con kế thừa một abstract class, nó phải triển khai tất cả các phương thức abstract trong abstract class hoặc tự được đánh dấu là abstract class. Trong TypeScript, ta đánh dấu một abstract class bằng cách sử dụng từ khóa abstract trước tên lớp.

Abstract class sau đây khai báo một phương thức abstract find và cũng bao gồm một phương thức thông thường display.

abstract class Person { name: string; constructor(name: string){ this.name = name; } display(): void{ console.log(this.name); } abstract find(name: string): Person; } class Employee extends Person { empCode: number; constructor(name: string, code: number) { super(name); this.empCode = code; } find(name: string){ return new Employee(name,1) } } let emp: Person = new Employee("James", 100); emp.display(); //James let emp2: Person = emp.find("Steve")

Trong ví dụ trên, Person là một abstract class bao gồm một thuộc tính và hai phương thức, trong đó một phương thức được khai báo là abstract. Phương thức find() là một phương thức abstract nên phải được định nghĩa trong lớp dẫn xuất. Lớp Employee kế thừa từ lớp Person, do đó nó phải định nghĩa phương thức find() là abstract. Lớp Employee phải triển khai tất cả các phương thức abstract của lớp Person, nếu không, trình biên dịch sẽ hiển thị một lỗi.

Abstract class cũng có thể bao gồm một thuộc tính abstract, như được minh họa dưới đây.

abstract class Person { abstract name: string; display(): void{ console.log(this.name); } } class Employee extends Person { name: string; empCode: number; constructor(name: string, code: number) { super(); // must call super() this.empCode = code; this.name = name; } } let emp: Person = new Employee("James", 100); emp.display(); //James

Tài liệu tham khảo: