Constructor trong TypeScript
Xem thêm các chuyên mục:

Là một website được viết trên công nghệ web Flutter vì vậy hỗ trợ rất tốt cho người học, kể cả những người học khó tính nhất.
Hiện tại website đang tiếp tục được cập nhập nội dung cho phong phú và đầy đủ hơn. Mong các bạn nghé thăm và ủng hộ website mới của chúng tôi.


Constructor (phương thức khởi tạo) là một phương thức đặc biệt của lớp, được sử dụng để tạo ra đối tượng và khởi tạo các giá trị cho các trường (field).
Để đơn giản, chúng ta sẽ phân tích tình huống dưới đây:

Lớp Person được coi là một bản thiết kế để tạo ra các con người cụ thể. Nó bao gồm các thông tin: Tên, giới tính và quốc gia. Các thông tin này còn được gọi là các trường (field).
Đặc điểm của Constructor:
- Constructor là một phương thức đặc biệt của lớp, từ khoá constructor cũng chính là tên của phương thức đặc biệt này.
- Mỗi lớp chỉ có duy nhất một constructor.
- Trong thân (body) của constructor bạn phải gán giá trị cho tất cả các trường (field) của lớp, các trường tuỳ chọn có thể không cần gán giá trị.
- Nếu tất cả các trường (field) trong lớp là tuỳ chọn, và bạn không định nghĩa bất kỳ constructor nào cho một lớp, TypeScript sẽ tự động coi rằng lớp này có một constructor mặc định, không có tham số và không có nội dung trong thân.
Ngôn ngữ TypeScript được thiết kế chặt chẽ và an toàn, nó đòi hỏi tất các trường (field) của lớp phải được gán giá trị khác null. Nếu một một trường nào đó cho phép giá trị null bạn phải nói rõ điều đó trong thiết kế của lớp.
Trong hầu hết các trường hợp sử dụng, dưới đây là cú pháp thông dụng để định nghĩa một constructor:
class Class_Name {
field_1 : data_type_f1;
field_2 : data_type_f2;
field_N : data_type_fN;
constructor(arg_1 : data_type_a1, arg_2 : data_type_a2, arg_M : data_type_aM) {
// Codes..
}
}
Trong ví dụ dưới đây, lớp Person có 3 trường, không có trường nào là tuỳ chọn, vì vậy trong thân của constructor bạn phải gán giá trị khác null cho tất cả các trường này.
constructor_ex1.ts
class Person {
name: string;
gender: string;
country: string;
constructor(n: string, g: string, c: string) {
this.name = n;
this.gender = g;
this.country = c;
}
// A Method
selfIntroduce(): void {
console.log(`Hi, My name is ${this.name}, from ${this.country}`);
}
}
function constructor_ex1_test() {
var tom: Person = new Person("Tom", "Male", "USA");
tom.selfIntroduce();
console.log(`tom.name = ${tom.name}`);
}
constructor_ex1_test(); // Call the function.
Output:
Hi, My name is Tom, from USA
tom.name = Tom
Ví dụ:
constructor_ex2.ts
interface IDimension {
width: number;
height: number;
}
class Rectangle {
width: number;
height: number;
color: string;
constructor(dimension: IDimension, color: string) {
this.width = dimension.width;
this.height = dimension.height;
this.color = color;
}
// A Method
getArea(): number {
return this.width * this.height;
}
}
function constructor_ex2_test() {
var dim: IDimension = { width: 10, height: 20 };
var rec: Rectangle = new Rectangle(dim, "blue");
console.log(`rec.getArea() = ${rec.getArea()}`);
console.log(`rec.color = ${rec.color}`);
}
constructor_ex2_test(); // Call the function
Sử dụng tham số tuỳ chọn trong một constructor là cách để bạn có thể sử dụng constructor này theo nhiều cách khác nhau. Chú ý: Các tham số tuỳ chọn phải là các tham số cuối cùng trong danh sách các tham số.
constructor(arg1 : data_type1, arg2 data_type2, arg3?: data_type3, arg4?: data_type4) {
// code..
}
Ví dụ:
constructor_optional_args_ex1_test.ts
class Circle {
radius: number;
color: string;
constructor(radius: number, color?: string) {
this.radius = radius;
this.color = color ?? "blue"; // Default value
}
}
function constructor_optional_args_ex1_test() {
var circle1 = new Circle(100);
var circle2 = new Circle(100, "red");
console.log(`circle1.color = ${circle1.color}`); // blue
console.log(`circle2.color = ${circle2.color}`); // red
}
constructor_optional_args_ex1_test(); // Call the function.
Output:
circle1.color = blue
circle2.color = red
Sử dụng tham số với giá trị mặc định trong một constructor cũng là cách để bạn có thể sử dụng constructor này theo nhiều cách khác nhau. Chú ý: Các tham số này phải là các tham số cuối cùng trong trong danh sách các tham số.
constructor(arg1 : data_type1, arg2 : data_type2,
arg3 : data_type3 = defaultValue3,
arg4 : data_type4 = defaultValue4) {
// code..
}
Ví dụ:
constructor_default_args_ex1.ts
class Square {
width: number;
color: string;
shadows: boolean;
constructor(width: number, color: string = "blue", shadows: boolean = true) {
this.width = width;
this.color = color;
this.shadows = shadows;
}
}
function constructor_default_args_ex1_test() {
var square1 = new Square(100);
var square2 = new Square(100, "red");
var square3 = new Square(100, "red", false);
console.log(`square1.color = ${square1.color}`); // blue
console.log(`square1.shadows = ${square1.shadows}`); // true
console.log(`square2.color = ${square2.color}`); // red
console.log(`square2.shadows = ${square2.shadows}`); // true
console.log(`square3.color = ${square3.color}`); // red
console.log(`square3.shadows = ${square3.shadows}`); // false
}
constructor_default_args_ex1_test(); // Call the function.
Output:
square1.color = blue
square1.shadows = true
square2.color = red
square2.shadows = true
square3.color = red
square3.shadows = false
Tham số trong TypeScrpit có thể được khai báo với kiểu dữ liệu liên hợp (union data type), đây cũng là một kỹ thuật để bạn có thể sử dụng constructor theo nhiều cách khác nhau.
constructor(arg1 : data_type1,
arg2 : data_type21 | data_type22 | data_type23,
arg3 : data_type3,
arg4 : data_type4) {
// code..
}
Ví dụ:
constructor_union_type_args_ex1.ts
class Employee {
name: string;
hireDate: Date;
department: string;
constructor(name: string, hireDate: Date | string, department: string) {
this.name = name;
this.department = department;
if(hireDate instanceof Date) {
this.hireDate = hireDate;
} else {
this.hireDate = new Date(hireDate);
}
}
}
function constructor_union_type_args_ex1_test() {
var tom = new Employee("Tom", new Date("1995-05-01"), "IT");
var jerry = new Employee("Jerry", "2001-05-01", "Operations");
console.log(`tom.hireDate = ${tom.hireDate}`);
console.log(`jerry.hireDate = ${jerry.hireDate}`);
}
constructor_union_type_args_ex1_test(); // Call the function.
Output:
tom.hireDate = Mon May 01 1995 06:00:00 GMT+0600 (GMT+06:00)
jerry.hireDate = Tue May 01 2001 06:00:00 GMT+0600 (GMT+06:00)
Như đã đề cập ở trên, TypeScript chỉ cho phép duy nhất một constructor trong một lớp. Constructor Overloading (nạp chồng phương thức khởi tạo) là một kỹ thuật "lách luật" trên. Nghĩa là bạn vẫn chỉ có một constructor trong một lớp nhưng có thể sử dụng nó với nhiều kiểu tham số khác nhau.
Trong TypeScript, Constructor Overloading trông khác với trong C++, Java hoặc C#. Ý tưởng chính để nạp chồng (overload) cho constructor là tạo một constructor chung để kiểm tra loại tham số nào đã được truyền để tạo một đối tượng 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 constructor để giúp các lập trình viên khác biết cách sử dụng lớp một cách thích hợp.
Cú pháp:
constructor(arg_11 : data_type_11, arg_1N : data_type_1N); // Definition 1
constructor(arg_21 : data_type_21, arg_22 : data_type_22, arg_2M : data_type_2M); // Definition 2
constructor(... args : any[]) {
// Constructor body.
}
- TODO Link?
Về cơ bản, có rất nhiều cách để định nghĩa một constructor nạp chồng. Cú pháp trên được khuyến khích sử dụng, nó giúp bạn tránh được thông báo lỗi giống như dưới đây từ trình biên dịch:
This overload signature is not compatible with its implementation signature.
Constructor overloading example 1:
constructor_overload_ex1.js
interface IDimension3D {
width: number;
height: number;
depth: number;
}
class Box {
width: number;
height: number;
depth: number;
color: string;
constructor(dimension: IDimension3D, color?: string); // definition 1
constructor(width: number, height: number, depth: number, color?: string); // definition 2
constructor(...args: any[]) {
if (args.length == 1 || args.length == 2) { // Use definition 1
this.width = args[0].width;
this.height = args[0].height;
this.depth = args[0].depth;
this.color = args.length == 1? "blue" : args[1];
} else if (args.length == 3 || args.length == 4) { // Use definition 2
this.width = args[0];
this.height = args[1];
this.depth = args[2];
this.color = args.length == 3? "blue" : args[3];
} else {
this.width = this.height = this.depth = 0;
this.color = "blue";
}
}
}
function constructor_overload_ex1_test() {
var dim: IDimension3D = { width: 10, height: 20, depth: 30};
var box1: Box = new Box(dim);
var box2: Box = new Box(dim, "red");
var box3: Box = new Box(100, 200, 300);
var box4: Box = new Box(100, 200, 300, "green");
console.log(`box1.width = ${box1.width}, box1.color = ${box1.color}`); // 10 , blue
console.log(`box2.width = ${box2.width}, box2.color = ${box2.color}`); // 10 , red
console.log(`box3.width = ${box3.width}, box3.color = ${box3.color}`); // 100 , blue
console.log(`box4.width = ${box4.width}, box4.color = ${box4.color}`); // 100 , green
}
constructor_overload_ex1_test(); // Call the function.
Output:
box1.width = 10, box1.color = blue
box2.width = 10, box2.color = red
box3.width = 100, box3.color = blue
box4.width = 100, box4.color = green
Constructor overloading example 2:
constructor_overload_ex2.ts
interface IPoint {
x: number;
y: number;
}
class Line {
x1: number; y1: number; x2: number; y2: number;
color: string;
constructor(point: IPoint, color?: string); // definition 1
constructor(point1: IPoint, point2: IPoint, color?: string); // definition 2
constructor(...args: any[]) {
if (args.length == 1) { // Use definition 1
this.x1 = this.y1 = 0;
this.x2 = args[0].x;
this.y2 = args[0].y;
this.color = "blue";
} else if (args.length == 2) {
if (typeof args[1] == "string") { // Use definition 1
this.x1 = this.y1 = 0;
this.x2 = args[0].x;
this.y2 = args[0].y;
this.color = args[1]
} else { // Use definition 2
this.x1 = args[0].x;
this.y1 = args[0].y;
this.x2 = args[1].x;
this.y2 = args[1].y;
this.color = "blue";
}
} else if (args.length >= 2) { // Use definition 3
this.x1 = args[0].x;
this.y1 = args[0].y;
this.x2 = args[1].x;
this.y2 = args[1].y;
this.color = args[2];
} else {
this.x1 = this.y1 = this.x2 = this.y2 = 0;
this.color = "blue";
}
}
}
function constructor_overload_ex2_test() {
var point1: IPoint = { x: 10, y: 20 };
var point2: IPoint = { x: 10, y: 20 };
var line1: Line = new Line(point1, point2);
var line2: Line = new Line(point2, "green");
console.log(`line1.color = ${line1.color}`); // blue
console.log(`line2.color = ${line2.color}`); // green
}
constructor_overload_ex2_test(); // Call the function.
Đôi khi việc sử dụng kỹ thuật "Constructor Overloading" như đã đề cập ở phần trên sẽ mang tới những phức tạp và khó hiểu trong quá trình sử dụng. Bạn nên cân nhắc sử dụng các phương thức nhà máy tĩnh (static factory method) như một giải pháp thay thế hiệu quả. Khác với constructor, bạn có thể tạo một hoặc nhiều phương thức nhà máy tĩnh.
static_factory_method_ex1.ts
interface IDimension3D {
width: number;
height: number;
depth: number;
}
class Box3D {
width: number;
height: number;
depth: number;
color: string;
constructor(width: number, height: number, depth: number, color: string) {
this.width = width;
this.height = height;
this.depth = depth;
this.color = color;
}
// Static factory method (To create Box3D object)
static fromDimension3D(dimension: IDimension3D, color: string): Box3D {
return new Box3D(dimension.width, dimension.height, dimension.depth, color);
}
}
function static_factory_method_ex1_test() {
var box1: Box3D = new Box3D(100, 200, 300, "red");
var dim: IDimension3D = { width: 10, height: 20, depth: 30 };
var box2: Box3D = Box3D.fromDimension3D(dim, "green"); // Call static method.
console.log(`box1.width = ${box1.width}, box1.color = ${box1.color}`); // 100 , red
console.log(`box2.width = ${box2.width}, box2.color = ${box2.color}`); // 10 , green
}
static_factory_method_ex1_test(); // Call the function.
Output:
box1.width = 100, box1.color = red
box2.width = 10, box2.color = green