Đa hình
Polymophism
Polymophism
Đa hình (polymophism) là một trong những tính chất quan trọng của OOP. Nó cho biết rằng các lớp khác nhau có thể thực hiện cùng một công việc theo những cách khác nhau.
Ví dụ có các lớp THinhVuong, THinhTron, ... là lớp con của THinh. Ta chỉ cần gọi THinh.DienTich (tính diện tích), tùy vào class nào thì sẽ có cách tính toán, xử lý phù hợp.
Một object được khai báo là class cha có thể được khởi tạo cho nhiều class con. Khi đó, ta chỉ cần một object của class cha thì có thể thay thế cho các object của class con.
Để có thể sử dụng được đa hình, các lớp con phải có chung một lớp cha nào đó. Các phương thức đa hình phải được ghi đè (override) ở các lớp con. Lớp con khi được khởi tạo bằng constructor của lớp nào thì sẽ mang kiểu của lớp con đó.
Chúng ta sẽ triển khai các lớp hình học như trên
type
THinh = class
function DienTich: Single; virtual; abstract;
end;
THinhVuong = class(THinh)
Canh: Integer;
function DienTich: Single; override;
end;
THinhTron = class(THinh)
BanKinh: Integer;
function DienTich: Single; override;
end;
function THinhVuong.DienTich: Single;
begin
Result := Canh * Canh;
end;
function THinhTron.DienTich: Single;
begin
Result := 3.14 * BanKinh * BanKinh;
end;
Như code trên, các bạn có thể thấy, ở mỗi class con (THinhVuong, THinhTron) thì có cách tính DienTich khác nhau. Method tính diện tích này đã được khai báo ở lớp cha, và được override ở lớp con.
Ở đây, lớp cha phải được chỉ định là virtual để lớp con có thể override được. Từ khóa abstract là để cho biết rằng, phương thức này ở lớp cha không được triển khai, nó sẽ được lớp con triển khai (như trên code không có đoạn THinh.DienTich).
Sau đây, sức mạnh của đa hình được phát huy. Khi dùng Create của lớp con nào, thì aHinh sẽ mang kiểu của lớp con đó (ví dụ aHinh là kiểu THinh, nhưng khởi tạo aHinh := THinhVuong.Create thì aHinh lúc này là THinhVuong)
var
aHinh: THinh;
begin
aHinh := THinhVuong.Create;
(aHinh as THinhVuong).Canh := 5;
writeln(aHinh.DienTich);
aHinh.Free;
aHinh := THinhTron.Create;
(aHinh as THinhTron).BanKinh := 10;
writeln(aHinh.DienTich);
aHinh.Free;
end;
Chỉ với một object aHinh (hình bất kì), chúng ta có thể làm cho nó biến thành hình vuông (THinhVuong), hoặc biến thành hình tròn (THinhTron) để tính cho phù hợp.
Trong delphi, phương thức trừu tượng (abstract method) là phương thức mà không được triển khai ở lớp hiện tại, lớp con sẽ thực hiện triển khai phương thức này.
Ví dụ như trên code ở phần trước, phương thức tính DienTich của THinh là một abstract method. Do đó, không có phần triển khai cho THinh.DienTich. Nhưng ở các subclass của THinh (là THinhVuong, THinhTron), method DienTich được triển khai lại.
Để chỉ định một method là abstract, sử dụng từ khóa abstract đặt vào cuối khai báo method (như trên).
Lưu ý: Abstract phải đi kèm với virtual. Ghi virtual trước rồi đến abstract. Lý do abstract phải có virtual bởi vì method ở parent class cần được ghi đè lại ở lớp con, nên phải đặt là virtual (Hoặc dùng dynamic thay cho virtual).
Lớp có ít nhất một abstract method là một abstract class.
Sealed class (lớp kín) là một class mà không thể kế thừa nó. Đồng nghĩa với việc nó không có bất kì class con nào.
Sử dụng từ khóa sealed đi kèm với class để chỉ định rằng một class là class sealed.
type
TFinalClass = class sealed
procedure DoSomeThing;
end;
Khi bạn cố gắng kế thừa một class sealed (như code sau), bạn sẽ nhận được một lỗi biên dịch (compile error) Can not extend sealed class 'ClassName'.
type
TTryInheritedSealedClass = class(TFinalClass)
...
end;
Đây là hai toán tử đặc biệt dùng trong đa hình. Toán tử is xác định một object có phải thuộc class hay không:
Object của class cha chỉ thuộc class cha
Object của class con vừa thuộc class cha, vừa thuộc class con
Biểu thức aObj is aClass trả về True khi aObj thuộc aClass, ngược lại trả về False.
type
TParent = class
end;
TSub = class
end;
var
Obj: TParent;
begin
Obj := TParent.Create;
if Obj is TParent then
writeln('TParent is TParent')
else
writeln('TParent is not TParent');
if Obj is TSub then
writeln('TParent is TSub')
else
writeln('TParent is not TSub');
Obj.Free;
Obj := TSub.Create;
if Obj is TParent then
writeln('TSub is TParent')
else
writeln('TSub is not TParent');
if Obj is TSub then
writeln('TSub is TSub')
else
writeln('TSub is not TSub');
end;
Nhờ đa hình, với mỗi lần khởi tạo Create bằng class TParent hoặc TSub, thì Obj sẽ mang kiểu của class Create. Chạy thử chương trình trên và nhận xét kết quả.
Nếu một object là thuộc một class, sử dụng từ khóa as để chuyển đổi lớp cho phù hợp. Mình lấy lại đoạn code hình vuông hình tròn ở trên xuống đây để các bạn xem cho dễ.
var
aHinh: THinh;
begin
aHinh := THinhVuong.Create;
(aHinh as THinhVuong).Canh := 5;
writeln(aHinh.DienTich);
aHinh.Free;
aHinh := THinhTron.Create;
(aHinh as THinhTron).BanKinh := 10;
writeln(aHinh.DienTich);
aHinh.Free;
end;
Như trên code, mình có sử dụng từ khóa as ở 2 dòng. Tại sao phải dùng as ? À vì aHinh được khai báo là THinh, mà trong lớp THinh không có trường Canh. Do đó, mình cần chuyển đổi aHinh từ THinh thành THinhVuong, để xuất hiện trường Canh. Đó là cách hoạt động của as.
Cấu trúc của as
var
Obj: TClass1;
...
(Obj as TClass2).
Khi đó, Obj as TClass2 sẽ là một object thuộc TClass2, không còn là TClass1 như đã khai báo nữa.
Thực tế, có một số trường hợp sử dụng song song is và as như sau
type
THocSinh = class
GioiTinh: boolean;
end;
var
LopHoc: array of TNguoi;
...
for Nguoi in LopHoc do
if Nguoi is THocSinh then
(Nguoi as THocSinh).DungLen;
Đoạn code đơn giản trên thực hiện nhiệm vụ: trong lớp học có nhiều người (mảng nhiều TNguoi), một số là học sinh (THocSinh).
Cho lặp hết từng người, nếu Nguoi là THocSinh (là học sinh) thì Nguoi as THocSinh (đối xử với người đó như là học sinh) sẽ đứng lên.
Đoạn mã trên là an toàn do chúng ta đã dùng is để kiểm tra chính xác đúng là THocSinh trước khi sử dụng as. Nếu không kiểm tra bằng is, thì lệnh as sẽ bị lỗi do đối xử với một người nào đó như là học sinh nhưng thực sự họ không phải.
Đó là lỗi Invalid class typecast. Xảy ra tại thời gian chạy (runtime).