Menu

Abstract class trong TypeScript: method và interface

Abstract class trong TypeScript là lớp cơ sở không thể tạo instance trực tiếp và có thể để lại phương thức cho lớp con triển khai. Tìm hiểu phương thức và thuộc tính abstract, mẫu template method, kiểu hàm khởi tạo abstract và khi nào interface là lựa chọn tốt hơn.

Trang này có trình soạn thảo chạy trực tiếp. Sửa, chạy và xem đầu ra ngay lập tức.

Abstract class (lớp trừu tượng) là lớp cơ sở được đánh dấu abstract. Nó không thể được tạo bằng new, và có thể khai báo các phương thức abstract không có thân hàm, mà mọi lớp con đều phải triển khai.

Shape chia sẻ describe() cho mọi lớp con và buộc từng lớp phải cung cấp area(). Kiểu Shape[] chứa được bất kỳ tổ hợp lớp con nào.

Không thể tạo instance từ abstract class

Gọi new trên một abstract class là lỗi biên dịch, vì đối tượng tạo ra sẽ có những phương thức không có phần triển khai:

index.ts(6,11): error TS2511: Cannot create an instance of an abstract class.

Đây là quy tắc lúc biên dịch. abstract bị xóa khỏi đầu ra, nên Shape được sinh ra là một lớp JavaScript bình thường, và code JavaScript không có kiểu vẫn có thể gọi new Shape(). Khi một thư viện cần chốt chặn lúc runtime, hàm khởi tạo có thể kiểm tra new.target:

Phương thức và thuộc tính abstract

Lớp con phải triển khai mọi thành viên abstract, nếu không thì chính nó cũng phải là abstract. Thiếu một thành viên sẽ gây lỗi TS2515, có nội dung kiểu Non-abstract class 'Square' does not implement inherited abstract member area from class 'Shape', hoặc TS2654 liệt kê tất cả khi thiếu nhiều thành viên.

Field, getter và phương thức đều có thể là abstract, và có thể là protected:

Phương thức abstract không có thân hàm; viết thân hàm cho nó sẽ gây lỗi TS1245 (Method 'x' cannot have an implementation because it is marked abstract). Một abstract class không nhất thiết phải có thành viên abstract nào: chỉ riêng abstract đã ngăn việc tạo instance trực tiếp.

Mẫu template method

Lý do phổ biến nhất để viết abstract class: lớp cơ sở cố định thứ tự các bước, còn mỗi lớp con điền vào những bước thay đổi.

Abstract class có thể là generic như ở đây, để mỗi lớp con tự chọn kiểu bản ghi.

Kiểu hàm khởi tạo abstract

Với một abstract class, typeof Shape là một hàm khởi tạo mà bạn không được phép gọi, nên một tham số có kiểu typeof Shape không thể làm new c(). Khi một hàm cần nhận abstract class (ví dụ để đăng ký chúng hoặc kiểm tra instanceof), hãy định kiểu bằng abstract construct signature:

abstract new nhận cả lớp abstract lẫn lớp cụ thể; new thông thường chỉ nhận lớp cụ thể.

Abstract class và interface

Abstract classInterface
Chứa codecó: phương thức, giá trị khởi tạo field, hàm khởi tạokhông, chỉ có kiểu
Tồn tại lúc runtimecókhông, bị xóa
instanceofdùng đượckhông thể
Số lượng một lớp dùng đượcmột (extends)nhiều (implements)
Access modifierprivate, protected, publicchỉ thành viên public
Làm kiểu cho đối tượng thườngchỉ khi không có thành viên private/protected, và khi đó instanceof sẽ saicó, mọi giá trị có cấu trúc phù hợp

Quy tắc chung: dùng interface khi bạn chỉ cần mô tả cấu trúc (trường hợp phổ biến nhất, và áp dụng được cho cả object literal), dùng abstract class khi các lớp con chia sẻ phần triển khai thật và bạn muốn trình biên dịch buộc chúng điền vào phần còn thiếu. Hai thứ này kết hợp tốt với nhau: interface cho hợp đồng công khai, abstract class làm một lớp cơ sở tiện dụng triển khai interface đó. Xem interface và type cho một phép so sánh thường gặp khác.

Lỗi thường gặp

  • Dùng abstract class ở nơi interface là đủ. Nếu lớp cơ sở không có code, nó chỉ là một interface nặng thêm lúc runtime và bị giới hạn kế thừa đơn.
  • Thử dùng static abstract. Không được phép. Thành viên static không thể là abstract.
  • Chờ lỗi runtime khi gọi new trên abstract class. Sẽ không có lỗi nào, trừ khi bạn tự viết phần kiểm tra new.target.
  • Gọi phương thức abstract từ hàm khởi tạo của lớp cơ sở. Nó chạy trước khi các field của lớp con được khởi tạo, nên phần triển khai sẽ thấy chúng là undefined.

Câu hỏi thường gặp

Abstract class trong TypeScript là gì?

Là lớp được đánh dấu abstract, không thể tạo bằng new và có thể khai báo các thành viên abstract không có phần triển khai. Lớp con bắt buộc phải triển khai mọi thành viên abstract. Đây là cách vừa chia sẻ code vừa bắt buộc một cấu trúc nhất định.

Abstract class và interface trong TypeScript khác nhau thế nào?

Abstract class có thể chứa code thật (phương thức đã triển khai, field có giá trị khởi tạo, hàm khởi tạo) và tồn tại lúc runtime, nên instanceof dùng được với nó; một lớp chỉ có thể kế thừa một lớp. Interface không chứa code, bị xóa khi biên dịch, và một lớp có thể triển khai nhiều interface. Dùng abstract class để chia sẻ hành vi, interface để mô tả cấu trúc.

Abstract class trong TypeScript có hàm khởi tạo được không?

Có. Hàm khởi tạo chạy khi lớp con gọi super(...). Bạn vẫn không thể gọi new trên chính abstract class đó (TS2511: Cannot create an instance of an abstract class).

Phương thức static có thể là abstract trong TypeScript không?

Không. abstract chỉ áp dụng cho thành viên instance; static abstract gây lỗi TS1243 ('static' modifier cannot be used with 'abstract' modifier). Để bắt buộc một cấu trúc static, hãy định kiểu hàm khởi tạo bằng một interface có chứa thành viên static đó rồi kiểm tra lớp với interface ấy.

Abstract class có được kiểm tra lúc runtime không?

Không. abstract bị xóa, nên lớp sau khi biên dịch là JavaScript bình thường và code JavaScript thuần vẫn có thể gọi new trên nó. Nếu điều đó quan trọng, hãy kiểm tra new.target trong hàm khởi tạo và ném lỗi.

Hình minh họa các ngôn ngữ lập trình của Coddy

Học lập trình cùng Coddy

Bắt đầu