Typescript-Cuộc phiêu lưu với OOP (phần 3)
Chào mừng các bạn quay trở lại với phần 3 của series Typescript — Cuộc phiêu lưu với OOP ( Tính Đa Hình )
Sau một ngày vất vả, sáng code dạo, tối làm vài cuốc quanh cầu Thị Nghè. Mình đã trở về để tiếp tục hành trình với các bạn đây. Mà trước khi đi thẳng đến câu chuyện của kỳ 3, mình xin phép các bạn đọc ở câu chuyện kỳ này rằng các ví dụ của mình có phần liên quan đến bộ truyện Anime mà mình yêu thích “Naruto”. Nếu bạn chẳng phải fan cứng của truyện cũng đừng lo lắng nhé, vì cũng sẽ có rất nhiều ví dụ thực tế khác mình chia sẻ trong bài viết.
1. Lối Mòn:
Nói đến tính Đa hình (Polymorphism) trong lập trình OOP, 100 bạn sinh viên thì 30 bạn chịu nghe giảng sẽ không thể quên được ví dụ về tính diện tích hình học =)). Cùng xem ví dụ dưới đây để nhớ về hồi ức xưa nhé 😀
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
export class HinhHoc { public tinhDienTich() { console.log("Chưa biết hình nào"); } } export class HinhTron extends HinhHoc { public void tinhDienTich() { console.log("Đây là Diện tích hình Tròn"); } } public class HinhChuNhat extends HinhHoc { public void tinhDienTich() { console.log("Đây là Diện tích hình Chữ nhật"); } } let hinhHoc = new HinhHoc(); hinhHoc.tinhDienTich(); // Đoạn code này bình thường, sẽ in ra "Chưa biết hình nào" // Có lúc hinhHoc đóng vai trò là HinhTron trong một ngữ cảnh nào đó hinhHoc = new HinhTron(); hinhHoc.tinhDienTich(); // Đoạn code này sẽ in ra "Đây là Diện tích Hình tròn" // Có lúc hinhHoc đóng vai trò là HinhChuNhat trong một ngữ cảnh nào đó hinhHoc = new HinhChuNhat(); hinhHoc.tinhDienTich(); // Đoạn code này sẽ in ra "Đây là Diện tích Chữ nhật" |
Vậy tại sao lại Đa hình? Bạn cũng biết đó, vốn dĩ OOP là một cách thức tư duy lập trình hướng thực tế, nên hiển nhiên các khái niệm của nó cũng phải sát với các đặc điểm trong thực tế. Trong đó có Đa hình. Trong thực tế, sự Đa hình được xem như một đối tượng đặc biệt, tùy vào từng hoàn cảnh đối tượng này sẽ mang một hình thái khác nhau. Sự “nhập vai” vào các hình dạng (đối tượng) khác nhau này giúp cho đối tượng Đa hình ban đầu có thể thực hiện những hành động khác nhau của từng đối tượng cụ thể. Chẳng hạn bạn là một diễn viên, bạn diễn đủ vai từ quần chúng, vai chính, vai phụ. Chắc chắn rằng khi bạn nhận vai quần chúng, cát xê của bạn sẽ thấp hơn vai phụ và vai chính. Do đó trong từng thời điểm, phụ thuộc vào việc bạn đang diễn vai gì, cách tính cát xê của bạn sẽ theo vai ấy.
Có một điều chắc chắn rằng. Nếu như không xem hành động “tính cát xê” của nhân viên như ví dụ trên kia là Đa hình, thì chúng ta vẫn cứ xây dựng được một hệ thống tính cát xê hoàn chỉnh, nhưng sẽ phức tạp hơn là nếu bạn biết kiến thức về Đa hình là gì.
Nhưng trên đây chưa phải là tất cả, hôm nay chúng ta hãy thử tiếp cận Polymorphism theo một cách hoàn toàn khác nhé.
2. Huyết kế giới hạn:
Câu chuyện về Naruto đã bắt đầu cách đây cả hơn 10 năm trước rồi, các FAN cứng của Naruto thì không thể không biết đến một thứ gọi là “Huyết Kế Giới Hạn” (HKGH). HKGH là những thuộc tính hay kỹ năng được truyền lại từ đời ông cha để lại trong cùng một dòng tộc. Tuy nhiên một điều thú vị là các kỹ năng và thuộc tính của đời con cháu không phải lúc nào cũng giống như tiền bối thời trước, mà mỗi đứa lại có một khả năng khác biệt. Điều này thật giống với định nghĩa của Đa Hình trong lập trình OOP (wikipedia)
Tính đa hình (polymorphism): Thể hiện thông qua việc gửi các thông điệp (message). Việc gửi các thông điệp này có thể so sánh như việc gọi các hàm bên trong của một đối tượng. Các phương thức dùng trả lời cho một thông điệp sẽ tùy theo đối tượng mà thông điệp đó được gửi tới sẽ có phản ứng khác nhau. Người lập trình có thể định nghĩa một đặc tính (chẳng hạn thông qua tên của các phương thức) cho một loạt các đối tượng gần nhau nhưng khi thi hành thì dùng cùng một tên gọi mà sự thi hành của mỗi đối tượng sẽ tự động xảy ra tương ứng theo đặc tính của từng đối tượng mà không bị nhầm lẫn.
Trong đó Sharingan là một trong 10 HKGH mạnh nhất. Huyết Kế Giới Hạn này cho phép người sử dụng làm được rất nhiều việc. Khả năng đầu tiên và được biết đến nhiều nhất của Sharingan là ghi nhớ tất cả kĩ thuật đã được chứng kiến cũng như bắt chước động tác của đối thủ. Thứ hai, nó có khả năng thôi miên đối thủ. Thuật này có thể dự đoán được động tác của bộ não bị thôi miên, và nhìn thấu mọi kĩ thuật đánh lừa.
Tác giả Kishimoto Masashi không biết trước đây có phải là dev hay không mà ổng phối hợp khá thành thục các tính chất của lập trình OOP trong bộ truyện này :D. Hãy xem ví dụ dưới đây để xem ổng vận dụng thế nào nhé.
Cùng là có huyết kế giới hạn Mangekyou Sharigan nhưng một người sẽ có một kỹ năng chiến đấu khác nhau.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
export class Sharingan { // Khả năng quan sát của Sharingan public kyNangQuanSat (){ console.log('Có thể quan sát vật di chuyển cực nhanh'); console.log('Đoán trước được chuyển động'); } // Khả năng chiến đấu của Sharingan public kyNangChienDau () { console.log('Ảo thuật'); console.log('Sao chép thuật'); } } export class MangekyouSharingan extends Sharingan { // Sau khi lên Mangekyou thì Sharingan có thêm khả năng phòng thủ tuyệt đối. public kyNangPhongThu() { console.log('Phòng thủ tuyệt đối Susano'); } } export class ItachiMangekyouSharingan extends MangekyouSharingan { // Ghi đè phương thức kyNangChienDau public kyNangChienDau () { super.kyNangChienDau(); console.log('Amaterasu(Lửa địa ngục)'); } } export class ObitoMangekyouSharingan extends MangekyouSharingan { // Ghi đè phương thức kyNangChienDau public kyNangChienDau () { super.kyNangChienDau(); console.log('Kamui(đổi chiều không gian)'); } } export class MadaraMangekyouSharingan extends MangekyouSharingan { // Ghi đè phương thức kyNangChienDau public kyNangChienDau () { super.kyNangChienDau(); console.log('Ảo thuật Tsukuyomi'); } } |
3. Dùng thế nào cho đúng?
Đến đây chắc chắn bạn đã hiểu sơ bộ khái niệm Đa hình. Vậy thì trong OOP chúng ta tổ chức và sử dụng đặc tính Đa hình này như thế nào?
Đa hình sẽ gắn liền với kế thừa. Và, Đa hình cũng sẽ gắn liền với ghi đè phương thức (overriding) và nạp chồng phương thức (Overloading) nữa. Bởi vì như trên đây có nói đó, Đa hình là nói đến một đối tượng nào đó có khả năng nhập vai thành các đối tượng khác. Vậy thì đối tượng đó phải có chung thuộc tính và phương thức thì mới có thể nhập vai được, ắt hẳn nó phải là đối tượng cha. Và để đối tượng cha có thể là một trong các đối tượng con ở từng hoàn cảnh, thì nó phải định nghĩa ra các phương thức để con của nó có thể ghi đè. Điều này giúp hệ thống xác định được đối tượng nào và phương thức nào thực sự đang hoạt động khi ứng dụng đang chạy. Nên nhiều tài liệu gọi Đa hình này là Đa hình tại runtime là vậy.
Ghi đè (overriding) trong Typescript:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
class Animal { name: string; constructor(theName: string) { this.name = theName; } move(distanceInMeters: number = 0) { console.log(`${this.name} moved ${distanceInMeters}m.`); } } class Snake extends Animal { constructor(name: string) { super(name); } move(distanceInMeters = 5) { console.log("Slithering..."); super.move(distanceInMeters); } } class Horse extends Animal { constructor(name: string) { super(name); } move(distanceInMeters = 45) { console.log("Galloping..."); super.move(distanceInMeters); } } let sam = new Snake("Sammy the Python"); let tom: Animal = new Horse("Tommy the Palomino"); sam.move(); tom.move(34); |
Bạn đã thấy đó, đối tượng Animal bản thân nó có một phương thức move(). Nhưng khác với cách sử dụng các đối tượng từ các bài học từ trước đến giờ, rằng mỗi khi cần đến các lớp con thực hiện move, chúng ta sẽ khai báo lớp con và gọi phương thức được override ở lớp con. Thì bài hôm nay chúng ta cho phép lớp Animal có khả năng đóng vai trò là lớp con, bằng cách khởi tạo lại đối tượng là lớp con của nó, let tom: Animal= new Hourse(“Tommy the Palomino”), rồi chính nó sẽ đóng vai là lớp con đó. Tính Đa hình là đây.
Nạp chồng (Overloading) trong Typescript:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
export class HinhHoc { draw (); draw (canh: number); draw (dai: number, rong: number); draw (param1?: number, param2?: number) { if (param1 === undefined && param2 === undefined) { console.log('Ve hinh'); } if (param2 === undefined) { console.log('Ve hinh vuong'); } if (param1 !== undefined && param2 !== undefined) { console.log('Ve hinh chu nhat'); } } } |
Có sự khác biết với các ngôn ngữ phổ biết khác như C# và Java, ví dụ dưới trên cho bạn biết cách làm sao để nạp chồng một phương thức trong Typescript.
4. Kết luận:
Đa hình là vậy, không quá khó hiểu đúng không nào. Sẽ còn kiến thức có liên quan đến Đa hình và Đa hình thì luôn đi kèm với Kế thừa nữa, đó là một phần lý do mình để câu chuyện về Kế thừa ở cuối series này. Các bạn sẽ nhận ra rằng tính chất Kế thừa là chìa khoá của lập trình OOP, là trung tâm cũng các tính chất mà chúng ta đã học trước đó.
Đón xem phần tiếp theo: Inheritance — Chìa khoá.
Lê Xuân Quỳnh – Developer @ JANETO
TRUNG TÂM ĐÀO TẠO LẬP TRÌNH VIÊN JANETO
THÔNG TIN LIÊN HỆ
Hotline: 0933 06 7997 – 0933267337
Fanpage: facebook.com/laptrinhvienio
Channel: YouTube/laptrinhvienio
Email: tuyensinh@laptrinhvien.io
Địa chỉ: Tầng 2, Tòa nhà The Morning Star – 57 Quốc lộ 13, Phường 26, Quận Bình Thạnh, Tp. Hồ Chí Minh.