Chương 5 Phương thức ảo và tính đa hình5.1 Bài toán quản lý một danh sách các đối tượng khác kiểu 5.2 Vùng chọn kiểu 5.3 Phương thức ảo 5.4 Phương thức thiết lập ảo 5.5 Phương thức ảo t
Trang 1Chương 5 Phương thức ảo và tính đa hình
5.1 Bài toán quản lý một danh sách các đối
tượng khác kiểu
5.2 Vùng chọn kiểu
5.3 Phương thức ảo
5.4 Phương thức thiết lập ảo
5.5 Phương thức ảo thuần tuý
Trang 25.1 Bài toán quản lý một danh sách các đối tượng khác
kiểu
- Giả sử ta cần quản lý một danh sách các đối tượng có kiểu có thể
khác nhau, ta cần giải quyết hai vấn đề: Cách lưu trữ và thao tác
xử lý.
- Xét trường hợp cụ thể, các đối tượng có thể là người, sinh viên hoặc công nhân.
- Về lưu trữ: Ta có thể dùng union, trong trường hợp này mỗi đối
tượng phải có kích thước chứa được đối tượng có kích thước lớn nhất Điều này gây lãng phí không gian lưu trữ Một cách thay thế là lưu trữ đối tượng bằng đúng kích thước của nó và dùng một danh sách (mảng, dslk, ) các con trỏ để quản lý các đối tượng
- Về thao tác, phải thoả yêu cầu đa hình: Thao tác có hoạt động khác nhau ứng với các loại đối tượng khác nhau Có hai cách giải
quyết là vùng chọn kiểu và phương thức ảo.
Trang 35.2 Dùng vùng chọn kiểu
Về lưu trữ: Ta sẽ dùng một mảng các con trỏ đến lớp cơ sở để
có thể trỏ đến các đối tượng thuộc lớp con
Xét lớp Người và các lớp kế thừa sinh viên và công nhân
Thao tác ta quan tâm là xuat Ta cần bảo đảm thao tác xuất áp
dụng cho lớp sinh viên và lớp công nhân khác nhau
Trang 4Dùng vùng chọn kiểuclass Nguoi
~Nguoi() {delete [] HoTen;}
void An() const { cout << HoTen << " an 3 chen com";}
void Ngu() const { cout << HoTen << " ngu ngay
8 tieng";}
void Xuat() const { cout << "Nguoi, ho ten: "
<< HoTen << " sinh " << NamSinh; }
};
Trang 5SinhVien(char *n, char *ms, int ns) :
Nguoi(n,ns) { MaSo = strdup(ms);}
~SinhVien() {delete [] MaSo;}
void Xuat() const { cout << "Sinh vien " <<
Trang 6Dùng vùng chọn kiểuclass CongNhan : public Nguoi
void Xuat() const { cout << "Cong nhan, ten "
<< HoTen << " muc luong: " << MucLuong;}
Trang 7Dùng vùng chọn kiểuconst int N = 4;
Trang 8Dùng vùng chọn kiểu
Xuất liệu cho đoạn chương trình trên như sau:
Nguoi, ho ten: Vien Van Sinh sinh 1982
Nguoi, ho ten: Le Thi Ha Dong sinh 1984
Nguoi, ho ten: Tran Nhan Cong sinh 1984
Nguoi, ho ten: Nguyen Thanh Nhan sinh 1960
Tất cả mọi đối tượng đều được quan điểm như người vì thao tác được thực hiện thông qua con trỏ đến lớp Người
Để bảo đảm xuất liệu tương ứng với đối tượng, phải có cách nhận diện đối tượng, ta thêm một vùng dữ liệu vào lớp cơ sở
để nhận diện, vùng này có giá trị phụ thuộc vào loại của đối tượng và được gọi là vùng chọn kiểu
Các đối tượng thuộc lớp người có cùng giá trị cho vùng chọn kiểu, các đối tượng thuộc lớp sinh viên có giá trị của vùng
chọn kiểu khác của lớp người
Trang 9~Nguoi() {delete [] HoTen;}
void An() const { cout << HoTen << " an 3 chen com";}
void Ngu() const { cout << HoTen << " ngu ngay 8 tieng";}
void Xuat() const { cout << "Nguoi, ho ten: " << HoTen << " sinh " << NamSinh; }
Trang 10SinhVien(char *n, char *ms, int ns) :
Nguoi(n,ns) { MaSo = strdup(ms); pl = SV;}
~SinhVien() {delete [] MaSo;}
void Xuat() const { cout << "Sinh vien " <<
Trang 11Dùng vùng chọn kiểuclass CongNhan : public Nguoi
void Xuat() const { cout << "Cong nhan, ten "
<< HoTen << " muc luong: " << MucLuong;}
};
Khi thao tác ta phải căn cứ vào giá trị của vùng chọn kiểu để “ép kiểu” phù hợp
Trang 13Dùng vùng chọn kiểuconst int N = 4;
Xuất liệu của đoạn chương trình trên sẽ là:
Sinh vien Vien Van Sinh, ma so 200001234
Sinh vien Le Thi Ha Dong, ma so 200001235
Cong nhan, ten Tran Nhan Cong muc luong: 1000000
Trang 14Dùng vùng chọn kiểu
Trang 15Dùng vùng chọn kiểu
Cách tiếp cận trên giải quyết được vấn để: Lưu trữ được các đối tượng khác kiểu nhau và thao tác khác nhau tương ứng với đối tượng Tuy nhiên nó có các nhược điểm sau:
– Dài dòng với nhiều switch, case
– Dễ sai sót, khó sửa vì trình biên dịch bị cơ chế ép kiểu che mắt
– Khó nâng cấp ví dụ thêm một loại đối tượng mới, đặc biệt khi chương trình lớn
Các nhược điểm trên có thể được khắc phục nhờ phương thức ảo
Trang 165.3 Phương thức ảo
Con trỏ thuộc lớp cơ sở có thể trỏ đến lớp con:
Nguoi* pn = new SinhVien(“Le Vien Sinh”, 200001234, 1982);
Ta mong muốn thông qua con trỏ thuộc lớp cơ sở có thể truy xuất hàm thành phần được định nghĩa lại ở lớp con:
pn->Xuat(); // Mong muon: goi Xuat cua lop sinh
// vien, thuc te: goi Xuat cua lop // Nguoi
Phương thức ảo cho phép giải quyết vấn đề Ta qui định một hàm thành phần là phương thức ảo bằng cách thêm từ khoá virtual vào trước khai báo hàm
Trong ví dụ trên, ta thêm từ khoá virtual vào trước khai báo của hàm xuat
Trang 17Phương thức ảoclass Nguoi
~Nguoi() {delete [] HoTen;}
void An() const { cout << HoTen << " an 3 chen com";}
void Ngu() const { cout << HoTen << " ngu ngay
8 tieng";}
virtual void Xuat() const { cout << "Nguoi, ho ten: " << HoTen << " sinh " << NamSinh; }
};
Trang 18SinhVien(char *n, char *ms, int ns) :
Nguoi(n,ns) { MaSo = strdup(ms);}
~SinhVien() {delete [] MaSo;}
void Xuat() const { cout << "Sinh vien " <<
Trang 20Phương thức ảoconst int N = 4;
Trang 21Phương thức ảoNguoi *pn;
pn = new SinhVien("Vien Van Sinh", "200001234", 1982);
pn->Xuat(); // Goi thao tac xuat cua lop Sinh vien
Con trỏ pn thuộc lớp Nguoi nhưng trỏ đến đối tượng sinh
viên, vì vậy pn->Xuat() thực hiện thao tác xuất của lớp sinh viên
Trở lại ví dụ trên, khi i a[i] lần lượt trỏ đến các đối tượng
thuộc các loại khác nhau, thao tác tương ứng với lớp sẽ được gọi
Dùng phương thức ảo khắc phục được các nhược điểm của cách tiếp cận dùng vùng chọn kiểu:
Thao tác đơn giản không phải dùng switch/case vì vậy khó sai, dễ sửa
Trang 22Thêm lớp con mới
Dùng phương thức ảo, ta dễ dàng nâng cấp sửa chữa Việc
thêm một loại đối tượng mới rất đơn giản, ta không cần phải sửa đổi thao tác xử lý (hàm XuatDs) Qui trình thêm chỉ là xây dựng lớp con kế thừa từ lớp cơ sở hoặc các lớp con đã có và định nghĩa lại phương thức (ảo) ở lớp mới tạo nếu cần
class CaSi : public Nguoi
{
protected:
double CatXe;
public:
CaSi(char *ht, double cx, int ns) : Nguoi(ht,ns), CatXe(cx) {}
void Xuat() const { cout << "Ca si, " << HoTen << " co cat xe " << CatXe;}
};
Trang 23Thêm lớp con mớivoid XuatDs(int n, Nguoi *an[])
Trang 24Các lưu ý khi sử dụng phương thức ảo
Phương thức ảo chỉ hoạt động thông qua con trỏ
Muốn một hàm trở thành phương thức ảo có hai cách: Khai
báo với từ khoá virtual hoặc hàm tương ứng ở lớp cơ sở đã là phương thức ảo
Phương thức ảo chỉ hoạt động nếu các hàm ở lớp cơ sở và lớp con có nghi thức giao tiếp giống hệt nhau
Nếu ở lớp con định nghĩa lại phương thức ảo thì sẽ gọi phương thức ở lớp cơ sở (gần nhất có định nghĩa)
Trang 25Ví dụ thêm về phương thức ảo
Một ví dụ tương tự về sử dụng phương thức ảo là quản lý một danh sách các động vật Xem mamal.cpp
Trang 26Cơ chế thực hiện phương thức ảo
Khi gọi một thao tác, khả năng chọn đúng phiên bản tuỳ theo đối tượng để thực hiện thông qua con trỏ đến lớp cơ sở được gọi là tính đa hình (polymorphisms)
Cơ chế đa hình được thực hiện nhờ ở mỗi đối tượng có thêm một bảng phương thức ảo Bảng này chứa địa chỉ của các
phương thức ảo và nó được trình biên dịch khởi tạo một cách ngầm định khi thiết lập đối tượng
Khi thao tác được thực hiện thông qua con trỏ, hàm có địa chỉ trong bảng phương thức ảo sẽ được gọi
Trong ví dụ trên, mỗi đối tượng thuộc lớp cơ sở Người có
bảng phương thức ảo có một phần tử là địa chỉ hàm
Nguoi::Xuat Mỗi đối tượng thuộc lớp SinhVien có bảng
tương tự nhưng nội dung là địa chỉ của hàm SinhVien::Xuat
Trang 27Cơ chế thực hiện phương thức ảo
Trong ví dụ 2, mỗi đối tượng thuộc các lớp Mamal, Dog, Cat, Horse, Pig đều có bảng phương thức ảo với hai phần tử, địa chỉ của hàm Speak và của hàm Move
Trang 29Phương thức huỷ bỏ ảo
Trong ví dụ quản lý danh sách các đối tượng thuộc các lớp Nguoi, SinhVien, CongNhan, … Thao tác dọn dẹp đối tượng
a[0] = new SinhVien("Vien Van Sinh", "20001234", 1982);
a[1] = new NuSinh("Le Thi Ha Dong", "20001235", 1984);
a[2] = new CongNhan("Tran Nan Cong", 1000000, 1984);
a[3] = new Nguoi("Nguyen Thanh Nhan", 1960);
XuatDs(4,a);
for (int i = 0; i < 4; i++)
delete a[i];
}
Trang 30Phương thức huỷ bỏ ảo
Thông qua con trỏ thuộc lớp cơ sở Nguoi, chỉ có phương thức huỷ bỏ của lớp Nguoi được gọi
Để bảo đảm việc dọn dẹp là đầy đủ, ta dùng phương thức huỷ
Nguoi(char *ht, int ns):NamSinh(ns) {HoTen = strdup(ht);}
virtual ~Nguoi() {delete [] HoTen;}
virtual void Xuat(ostream &os) const { os << "Nguoi, ho ten: " <<
HoTen << " sinh " << NamSinh; }
void Xuat() const { Xuat(cout); }
};
Trang 315.4 Phương thức thiết lập ảo
C++ không cung cấp cơ chế thiết lập đối tượng có khả năng đa hình theo cơ chế hàm thành phần ảo
Tuy nhiên ta có thể “thu xếp” để có thể tạo đối tượng theo
nghĩa “ảo” Xem ví dụ pttl_ao.cpp
Phương thức thiết lập ảo cũng có thể được hiện thực bằng cách dùng hàm thành phần tĩnh để tạo đối tượng
Trang 32Phương thức thiết lập ảoenum FILETYPE {UNKNOWN, BMP, GIF, JPG};
virtual void Release() = 0;
static CGBmpPtr NewBmp(const String &pathName); virtual void Draw() const;
};
Trang 33Phương thức thiết lập ảoclass CBmp : public CGBmp
Trang 34Phương thức thiết lập ảo
String FileExt = GetFileExt(pathName);
if (FileExt == ".DIB" || FileExt == ".BMP")
Trang 35Phương thức thiết lập ảo
CGBmpPtr CGBmp::NewBmp(const String &pathName)
Trang 365.5 Phương thức ảo thuần tuý và lớp cơ sở trừu tượng
Lớp cơ sở trừu tượng là lớp cơ sở không có đối tượng nào
thuộc chính nó Một đối tượng thuộc lớp cơ sở trừu tượng phải thuộc một trong các lớp con
Xét các lớp Circle, Rectangle, Square kế thừa từ lớp Shape, xem chương trình nguồn pta_tt.cpp
Trong ví dụ trên, các hàm trong lớp Shape có nội dung nhưng nội dung không có ý nghĩa Đồng thời ta luôn luôn có thể tạo được đối tượng thuộc lớp Shape, điều này không đúng với tư tưởng của phương pháp luận hướng đối tượng
Ta có thể thay thế cho nội dung không có ý nghĩa bằng
phương thức ảo thuần tuý Phương thức ảo thuần tuý là
phương thức ảo không có nội dung
Trang 37Phương thức ảo thuần tuý và lớp cơ sở trừu tượng
Khi lớp có phương thức ảo thuần tuý, lớp trở thành lớp cơ sở trừu tượng Ta không thể tạo đối tượng thuộc lớp cơ sở thuần tuý
Ta có thể định nghĩa phương thức ảo thuần tuý, nhưng chỉ có các đối tượng thuộc lớp con có thể gọi nó Xem pta_tt2
Trong ví dụ trên, các hàm thành phần trong lớp Shape là
phương thức ảo thuần tuý Nó bảo đảm không thể tạo được đối tượng thuộc lớp Shape Ví dụ trên cũng định nghĩa nội dung cho phương thức ảo thuần tuý, nhưng chỉ có các đối tượng
thuộc lớp con có thể gọi
Phương thức ảo thuần tuý có ý nghĩa cho việc tổ chức sơ đồ phân cấp các lớp, nó đóng vai trò chừa sẵn chỗ trống cho các lớp con điền vào với phiên bản phù hợp
Trang 38Phương thức ảo thuần tuý và lớp cơ sở trừu tượng
Bản thân các lớp con của lớp cơ sở trừu tượng cũng có thể là lớp cơ sở trừu tượng, như ví dụ pta_tt3.cpp