Lập trình hướng đối tượng - Chương 6: Đa hình - Huỳnh Quyết Thắng
Function overloading - Hàm chồng: dùng một
tên hàm cho nhiều định nghĩa hàm, khác nhau ở
danh sách tham số
Method overloading – Phương thức chồng:
tương tự
void jump(int howHigh);
void jump(int howHigh, int howFar);
– hai phương thức jump trùng tên nhưng có danh sách
tham số khác nhau
Tuy nhiên, đây không phải đa hình hướng đối
tượng mà ta đã định nghĩa, vì đây thực sự là hai
thông điệp jump khác nhau.
loading vs Overriding Function overloading - Hàm chồng: dùng một tên hàm cho nhiều định nghĩa hàm, khác nhau ở danh sách tham số Method overloading – Phương thức chồng: tương tự void jump(int howHigh); void jump(int howHigh, int howFar); – hai phương thức jump trùng tên nhưng có danh sách tham số khác nhau Tuy nhiên, đây không phải đa hình hướng đối tượng mà ta đã định nghĩa, vì đây thực sự là hai thông điệp jump khác nhau. 3TS H.Q. Thắng - TS C.T. Dũng CNPM 5 Method Overriding Xét hành vi “draw” của các lớp trong cây phả hệ bên. – thông điệp “draw” gửi cho một thể hiện của mỗi lớp trên sẽ yêu cầu thể hiện đó tự vẽ chính nó. – một thể hiện của Point phải vẽ một điểm, một thể hiện của Circle phải vẽ một đường tròn, và một thể hiện của Rectangle phải vẽ một hình chữ nhật TS H.Q. Thắng - TS C.T. Dũng CNPM 6 Định nghĩa lại phương thức Để override một phương thức của một lớp cơ sở, phương thức tại lớp dẫn xuất phải có cùng tên, cùng danh sách tham số, cùng kiểu giá trị trả về, cùng là const hoặc cùng không là const Nếu lớp cơ sở có nhiều phiên bản overload của cùng một phương thức, việc override một trong các phương thức đó sẽ che tất cả các phương thức còn lại 4TS H.Q. Thắng - TS C.T. Dũng CNPM 7 Định nghĩa lại hành vi Draw (C++) class Point { public: Point(int x, int y, string color); ~Point(); void draw(); private: int x; int y; string color; }; class Circle : public Point { public: Circle (int x, int y, string color, int radius); ~Circle(); void draw(); private: int radius; }; ... void Point::draw() { // Draw a point } ... void Circle::draw() { // Draw a circle } Khai báo lại tại lớp kế thừa Cung cấp định nghĩa mới TS H.Q. Thắng - TS C.T. Dũng CNPM 8 Định nghĩa lại Draw() Kết quả: khi tạo các thể hiện của các lớp khác nhau và gửi thông điệp “draw”, các phương thức thích hợp sẽ được gọi. Point p(0,0,”white”); Circle c(100,100,”blue”,50); p.draw(); // Vẽ điểm Trắng tại (0,0) c.draw(); // Vẽ hình tròn xanh dương bán kính 50 tại (100,100) Ta cũng có thể tương tác với các thể hiện lớp dẫn xuất như thể chúng là thể hiện của lớp cơ sở Circle *pc = new Circle(100,100,”blue”,50); Point* pp = pc; Nhưng nếu có đa hình, lời gọi sau sẽ làm gì? pp->draw(); // Draw what??? 5TS H.Q. Thắng - TS C.T. Dũng CNPM 9 Ví dụ về đa hình: Bò điên Một con bò – Kêu Moo TS H.Q. Thắng - TS C.T. Dũng CNPM 10 Bò điên Bò điên – cũng là bò: nhưng tưởng mình là ếch kêu oap 6TS H.Q. Thắng - TS C.T. Dũng CNPM 11 Bò điên Như vậy, nếu tạo một đối tượng Cow, và gọi hành vi makeASound() của nó, nó sẽ kêu “moo” Cow bob(“Bob”, “brown”); bob.makeASound(); Ta còn có thể tạo một con trỏ tới đối tượng Cow, và làm cho nó kêu “moo” p_bob = &bob; p_bob->makeASound(); TS H.Q. Thắng - TS C.T. Dũng CNPM 12 Nhập nhằng Nếu coi nó là bò điên (MadCow), nó sẽ hành động đúng là bò điên MadCow maddy(“Maddy”, “white”); maddy.makeASound(); // Go “oap” MadCow * maddy = new MadCow(“Maddy”, “white”); maddy->makeASound(); // Go “oap” Còn khi tưởng nó chỉ là bò thường (Cow), nó lại có vẻ bình thường MadCow* maddy = new madCow(“Maddy”, “white”); Cow* cow = maddy; // Upcasting cow->makeASound(); // Go “moo” ??? Làm thế nào để bò điên lúc nào cũng điên (kêu “oap”)? 7TS H.Q. Thắng - TS C.T. Dũng CNPM 13 Liên kết lời gọi hàm function call binding Function call binding là quy trình xác định khối mã hàm cần chạy khi một lời gọi hàm được thực hiện C: đơn giản vì mỗi hàm có duy nhất một tên C++: chồng hàm, phân tích chữ ký kiểm tra danh sách tham số. TS H.Q. Thắng - TS C.T. Dũng CNPM 14 Ngôn ngữ HĐT method call binding Liên kết lời gọi phương thức Đối với các lớp độc lập (không thuộc cây thừa kế nào), quy trình này gần như không khác với function call binding – so sánh tên phương thức, danh sách tham số để tìm định nghĩa tương ứng – một trong số các tham số là tham số ẩn: con trỏ this 8TS H.Q. Thắng - TS C.T. Dũng CNPM 15 Liên kết tĩnh C/C++ function call binding, và C++ method binding cơ bản đều là ví dụ của static function call binding Static function call binding (hoặc static binding – liên kết tĩnh) là quy trình liên kết một lời gọi hàm với một định nghĩa hàm tại thời điểm biên dịch. – do đó còn gọi là “compile-time binding” – liên kết khi biên dịch, hoặc “early binding” – liên kết sớm TS H.Q. Thắng - TS C.T. Dũng CNPM 16 Liên kết tĩnh: trường hợp có cây thừa kế Cow bob(“Bob”,”brow”); MadCow maddy(“Maddy”, “white”); bob.makeASound(); // go “moo” maddy.makeASound(); // go “oap” MadCow& rMaddy = maddy; rMaddy.makeASound(); // Go “oap” MadCow* pMaddy = &maddy; pMaddy->makeASound(); // Go “oap” Cow& rCow = maddy; rCow.makeASound(); // Go “moo” Cow* pCow = &maddy; pCow->makeASound(); // Go “moo” Kiểu tĩnh: MadCow Kiểu tĩnh: Cow Sao không kêu oap??? 9TS H.Q. Thắng - TS C.T. Dũng CNPM 17 Liên kết tĩnh Với liên kết tĩnh, quyết định “định nghĩa hàm nào được chạy” được đưa ra tại thời điểm biên dịch - rất lâu trước khi chương trình chạy. thích hợp cho các lời gọi hàm thông thường – mỗi lời gọi hàm chỉ xác định duy nhất một định nghĩa hàm, kể cả trường hợp hàm chồng. phù hợp với các lớp độc lập không thuộc cây thừa kế nào – mỗi lời gọi phương thức từ một đối tượng của lớp hay từ con trỏ đến đối tượng đều xác định duy nhất một phương thức TS H.Q. Thắng - TS C.T. Dũng CNPM 18 Đa hình tĩnh Đa hình tĩnh thích hợp cho các phương thức: – được định nghĩa tại một lớp, và được gọi từ một thể hiện của chính lớp đó (trực tiếp hoặc gián tiếp qua con trỏ) – được định nghĩa tại một lớp cơ sở và được thừa kế public nhưng không bị override tại lớp dẫn xuất, và được gọi từ một thể hiện của lớp dẫn xuất đó trực tiếp hoặc gián tiếp qua con trỏ tới lớp dẫn xuất hoặc qua một con trỏ tới lớp cơ sở – được định nghĩa tại một lớp cơ sở và được thừa kế public và bị override tại lớp dẫn xuất, và được gọi từ một thể hiện của lớp dẫn xuất đó (trực tiếp hoặc gián tiếp qua con trỏ tới lớp dẫn xuất) 10 TS H.Q. Thắng - TS C.T. Dũng CNPM 19 Đa hình động Dynamic function call binding (dynamic binding – liên kết động) là quy trình liên kết một lời gọi hàm với một định nghĩa hàm tại thời gian chạy – còn gọi là “run-time” binding hoặc “late binding” Với liên kết động, quyết định chạy định nghĩa hàm nào được đưa ra tại thời gian chạy, khi ta biết chắc con trỏ đang trỏ đến đối tượng thuộc lớp nào. Đa hình động (dynamic polymorphisme) là loại đa hình cài đặt bằng liên kết động TS H.Q. Thắng - TS C.T. Dũng CNPM 20 Hàm ảo Hàm/phương thức ảo – virtual function/method là cơ chế của C++ cho phép cài đặt đa hình động Nếu khai báo một hàm thành viên (phương thức) là virtual, trình biên dịch sẽ đẩy lùi việc liên kết các lời gọi phương thức đó với định nghĩa hàm cho đến khi chương trình chạy. – nghĩa là, ta bảo trình biên dịch sử dụng liên kết động thay cho liên kết tĩnh đối với phương thức đó Để một phương thức được liên kết tại thời gian chạy, nó phải khai báo là phương thức ảo (từ khoá virtual) tại lớp cơ sở 11 TS H.Q. Thắng - TS C.T. Dũng CNPM 21 Quay lại ví dụ Bò điên TS H.Q. Thắng - TS C.T. Dũng CNPM 22 Hàm ảo Khai báo với từ khoá virtual trong định nghĩa lớp Không cần tại định nghĩa hàm, nếu định nghĩa hàm nằm ngoài định nghĩa lớp Một khi một phương thức được khai báolà hàm ảo tại lớp cơ sở, nó sẽ tự động là hàm ảo tại mọi lớp dẫn xuất trực tiếp hoặc gián tiếp. 12 TS H.Q. Thắng - TS C.T. Dũng CNPM 23 Điều kiện để thực hiện đa hình trong C++ Tồn tại sự kế thừa giữa các lớp theo một sơ đồ phân cấp nào đó Các lớp trong sơ đồ phân cấp này phải có hàm được khai báo với từ khoá virtual – phải có các hàm thành phần là virtual Để duyệt cây phân cấp phải khai báo và sử dụng con trỏ hoặc reference đến lớp cơ sở. Con trỏ hoặc reference được sử dụng để gọi các hàm thành phần ảo TS H.Q. Thắng - TS C.T. Dũng CNPM 24 Constructor và Destructor ảo Không thể khai báo các constructor ảo Có thể (và rất nên) khai báo destructor là hàm ảo. class A { public: A() { cout <<"A()"<< endl; p = new char[5]; } virtual ~A() { cout <<"~A()"<< endl; delete [] p; } private: char* p; }; 13 TS H.Q. Thắng - TS C.T. Dũng CNPM 25 Destructor ảo class Z : public A { public: Z() { cout <<"Z()"<< endl; q = new char[5000]; } ~Z() { cout <<"~Z()"<< endl; delete [] q; } private: char* q; }; TS H.Q. Thắng - TS C.T. Dũng CNPM 26 Đa hình trong Java Không còn khái niệm con trỏ Trong Java liên kết lời gọi phương thức mặc nhiên là động – Mọi phương thức mặc định là phương thức ảo. Như vậy chúng mặc nhiên bị "định nghĩa lại" bởi các phương thức lớp kế thừa – Trừ các phương thức tĩnh (liên kết tĩnh) Thực hiện đơn giản hơn nhiều so với C++ 14 TS H.Q. Thắng - TS C.T. Dũng CNPM 27 Ví dụ với Java TS H.Q. Thắng - TS C.T. Dũng CNPM 28 Ví dụ với Java ... public static void handleShapes(Shape[] shapes){ // Cac hinh ve se duoc ve theo cach rieng cua chung for( int i = 0; i < shapes.length; ++i) { shapes[i].draw(); } ... //Ta don gian goi phuong thuc xoa khong quan tam den // doi tuong thuoc lop nao for( int i = 0; i < shapes.length; ++i) { shapes[i].erase(); } } ... 15 TS H.Q. Thắng - TS C.T. Dũng CNPM 29 Tổng kết phần KTLTHDT Các đối tượng là các thành phần phần mềm có thể sử dụng lại, mô hình các thực thể trong thế giới thực Đối tượng có các thuộc tính biểu hiện trạng thái của nó, có các hành vi cho phép chúng chuyển đổi từ trạng thái này sang trạng thái khác TS H.Q. Thắng - TS C.T. Dũng CNPM 30 Tổng kết phần KTLTHDT Thiết kế HĐT là quá trình: – mô hình hóa thế giới thực – mô hình hóa sự tương tác giữa các đối tượng – đóng gói các thuộc tính và phương thức – che giấu thông tin giữa các đối tượng – đảm bảo tương tác hiệu quả thông qua một giao diện được định nghĩa tốt.
File đính kèm:
- lap_trinh_huong_doi_tuong_chuong_6_da_hinh_huynh_quyet_thang.pdf