Class, Interface trong TypeScript khác gì với C#, Java (Phần 3)
Class, Interface trong TypeScript khác gì với C#, Java (Phần 3)

Class, Interface trong TypeScript khác gì với C#, Java (Phần 3)

Published
November 7, 2022
Author
Nguyen Thanh Dat

14. Compare 2 function

Nếu như việc so sánh các member ở dạng đơn giản như string, boolean hay number, ... là khá đơn giản, câu hỏi đặt ra lúc này là: Khi nào thì 2 function được coi là compatible?
Bắt đầu với ví dụ nhỏ sau:
let x = (a: number) => 0; let y = (b: number, c: string) => 0; y = x ; // OK x = y; // Error !
Đầu tiên khi kiểm tra xem 2 function có assign được cho nhau không, ta sẽ kiểu tra parameter list đầu tiên. Mỗi parameter trong x phải có 1 parameter tương ứng trong y (Chú ý 2 parameter chỉ cần tương ứng về type, không cần phải trùng tên). Vì trong ví dụ, y có 1 param thuộc type string mà x không có => compiler sẽ báo lỗi.
Ngược lại, việc để thừa params vẫn được chấp nhận, nhưng trong trường hợp y = x. Lý do cho việc này là bởi vì hành vi ignore function parameters cũng thường gặp trong JavaScript. Ví dụ nhưng hàm forEach của JavaScript.
let items = ['a', 'b', 'c']; // In ra 'a', 'b', 'c' items.forEach((item, index, array) => console.log(item)); // Như thế này cũng vẫn được, 2 param index và array có thể bỏ qua. items.forEach(item => console.log(item));
Tiếp theo, ta so sánh kiểu trả về của function:
let x = () => ( { name: 'Tom' }); let y = () => ( { name: 'Tom', age: 17 } ); x = y ; // OK , pass. y = x; // Error !
Trong trường hợp trên, type system báo lỗi, yêu cầu kiểu trả về của function x  phải chứa đầy đủ các thành phần của kiểu trả về của y.

15. Dùng Interface hay Class?

Ok. Bây giờ, sau khi đã tìm hiểu qua về 2 khái niệm ClassInterface và 1 số điểm khác biệt của chúng trong TypeScript, tiếp theo, ta sẽ so sánh 2 khái niệm này và các trường hợp nên sử dụng chúng.
Quay trở lại với các ngôn ngữ OOP truyền thống (C#, Java, ...): Mục đích của interface trong các ngôn ngữ này đó là tạo ràng buộc về mặt hành vi. Một Class implement 1 interface thì bắt buộc phải implement các hành vi nói trên.
Ngược lại, trong Typescript, interface bao gồm cả các ràng buộc về behavior (function) cũng như structure của Class (Property).
Hay nói cách khác, 2 cách viết dưới đây hoàn toàn có thể chấp nhận.
interface Human { name: string; } class Teacher implements Human { name: string; }
class Human { name: string; } class Teacher extends Human { // ... }
Thực vậy, trong Angular Style Guide, class được khuyến khích sử dụng thay vì interface:
Why? TypeScript guidelines discourage the I prefix.
Why? A class alone is less code than a class-plus interface.
Why? A class can act as an interface (use implements instead of extends).
Why? An interface class can be a provider lookup token in Angular dependency injection.
Viết class còn ngắn hơn viết interface, đồng thời mọi thứ của interface có thể thực hiện được bằng class, vậy interface để làm gì?

16. Khi nào Interface nên được sử dụng?

Cho đến lúc này, khi viết code, việc sử dụng class hay interface là không có gì khác việt. Sự khác biệt chỉ xảy ra khi compile code từ TypeScript thành JavaScript để chạy:
The real different comes when we consider our compiled JavaScript output.
Sử dụng ví dụ dưới đây: Ta có khai báo 1 interface Response để định dạng kiểu dữ liệu trả về từ API:
interface Response { status: number; // 200, 401, 404 ... message: string; } fetch('https://my-api.com').then((response: Response) => { if (response.status == 200) { console.log(response.message); } });
Compile đoạn code trên về JavaScript, mã đích của ta sẽ như sau:
fetch('https://my-api.com').then(function (response) { if (response.status == 200) { console.log(response.message); } });
Ta không có thấy bóng dáng của interface Response đâu cả ! Interface trong Typescript sẽ chỉ có vai trò nhắc hint, báo lỗi khi viết code bằng editor cũng như trong quá trình compile. Sau đó, nó sẽ được loại bỏ khỏi mã đích JavaScript.
Ngược lại, không giống như interface, một class của TypeScript sẽ sinh ra 1 JavaScript construct thực sự (Quay lại ví dụ phía đầu bài viết). Với ví dụ trên, nếu đổi interface thành class, ta sẽ sinh ra mã JavaScript sau:
var Response = (function() { function Response() { } return Response; })(); fetch('https://my-api.com').then(function (response) { if (response.status == 200) { console.log(response.message); } });
Sau khi compiler dịch code từ TypeScript sang JavaScript, class của ta sẽ biến thành dạng function trong JavaScript, được lưu trong mã nguồn cuối cùng của chương trình! Đoạn code khai báo biến Response trên hoàn toàn thừa thãi và làm tăng dung lượng mã đích.
Nếu ta có 1 application đủ lớn, và sử dụng bừa bãi class như một model type annotation, thì sẽ dẫn tới kết quả là dung lượng chương trình được biên dịch phình to ra đáng kể!
Vậy chỉ sử dụng Class khi ta có logic nghiệp vụ thực sự cần được implement để thực thi. Ngược lại, nếu chỉ dùng nó để tạo 1 ràng buộc kiểu cho params hay variable, ta nên dùng Interface.

17. Kết luận

Tổng kết lại, sau 3 phần, chúng ta đã tìm hiểu được:
  • ClassInterface trong TypeScript là gì và thực chất của chúng trong JavaSript là gì.
  • Những điểm mới của ClassInterface trong TypeScript, và cả những điểm trái ngược hoàn toàn với khái niệm của chúng trong các ngôn ngữ OOP truyền thống mà ta cần chú ý.
  • Khi nào nên dùng Class hoặc Interface.