Học tập‎ > ‎C#‎ > ‎

Thừa kế đa hình trong C#

Không thể tìm thấy URL thông số tiện ích
Người đăng bài : duaca

Thừa kế và đa hình trong C Sharp

Trong thực tế, chuyện sinh tồn và phát triển giống loài luôn thể hiện những đặc trưng vốn có đối với hầu như tất cả các loại động vật tồn tại trong tự nhiên, đó là: “thừa kế”. Hổ mẹ sinh ra con, hổ con đương nhiên sẽ thích ăn thịt sống của các loài động vật khác, hổ con đương nhiên cũng được trang bị móng vuốt để có thể săn mồi và cũng đương nhiên, khi lớn lên chúng sẽ có những bộ lông vằn vện giống như cha mẹ chúng … Hoặc như chú cún con nhà bạn, vì cha mẹ chú là chó nên đương nhiên chú cũng biết sủa, biết cắn, chú cũng biết bò bằng bốn chân … Chúng ta vẫn nói đó là do di truyền, là do chúng được thừa kế những đặc trưng của giống loài từ cha mẹ chúng.

Trong tự nhiên, “thừa kế” được xem như quy luật của tạo hóa, trong lập trình hướng đối tượng, việc cho phép “thừa kế” sẽ giúp cho lập trình viên có thể đạt được rất nhiều thuận lợi khi phát triển ứng dụng của mình. Từ khâu thiết kế chương trình cho đến việc xây dựng (coding) các lớp dành cho đối tượng, từ việc bảo trì chương trình cho đến phát triển ứng dụng trong tương lai. C Sharp là ngôn ngữ lập trình hướng đối tượng, chính vì thế khi phát triển phần mềm bằng C Sharp bạn cũng có thể xây dựng các lớp thừa kế từ những lớp đã có. Tuy nhiên hãy nhớ ! “C Sharp chỉ cho phép đơn thừa kế từ Class” tức là 1 lớp chỉ có thể thừa kế duy nhất từ 1 lớp khác mà thôi (Điều này cũng rất hợp với tự nhiên : 1 con chỉ có 1 cha duy nhất)

Thừa kế trong lập trình hướng đối tượng cho phép bạn có thể tạo ra 1 lớp mới có đầy đủ tất cả những tính chất và phương thức của 1 lớp đã có mà không phải viết lại bất cứ 1 dòng lệnh nào (trừ khi bạn muốn định nghĩa lại 1 phương thức nào đó ở lớp con, đây là vấn đề Override chúng ta sẽ đề cập đến ở phần kế tiếp).

Hãy quan sát hình minh họa dưới đây:

Giả sử trong chương trình của mình, chúng ta cần phải mô tả các đối tượng tham gia giao thông đường bộ. Các phương tiện giao thông đường bộ thường có những đặc điểm như có thể chở hàng, hoạt động bằng động cơ, mỗi xe đều có biển số để quản lý, có thể di chuyển từ điểm A đến điểm B …; như vậy chúng ta sẽ xây dựng 1 lớp cơ sở (Base class) tạm gọi là lớp “Phương tiện giao thông” và trong chương trình, khi cần phải định nghĩa các lớp như “Xe tải”, “Xe con”, “Xe máy” và “Xe đạp” chúng ta sẽ xây dựng các lớp này thừa kế từ lớp “Phương tiện giao thông” ở trên. Trong tình huống này ta thấy “Xe tải”, “Xe con”, “Xe máy” và “Xe đạp” đều có thể chở hàng, đều hoạt động bằng động cơ, đều có biển số (Giả sử xe đạp cũng có biển số – điều này có trong thời bao cấp trước đây) và đều có thể di chuyển từ nơi này đến nơi khác. Rõ ràng nhờ “thừa kế”, tất cả những đặc điểm chung dành cho phương tiện giao thông mà ta đã định nghĩa trong lớp “Phương tiện giao thông” đều được “di truyền” lại cho các lớp con thừa kế từ nó như “Xe tải”, “Xe con”, “Xe máy”, “Xe đạp”.

Khả năng cho phép thừa kế trong lập trình hướng đối tượng đem lại một số lợi ích trong quá trình phát triển ứng dụng như:

-          Tính nhất quán (Consistency): Những đối tượng có chung nguồn gốc thì sẽ có những đặc điểm chung giống nhau.

-          Tái sử dụng mã lệnh (Reusable): Những mã lệnh chung chỉ cần được định nghĩa 1 lần tại lớp cơ sở (Base class) là các lớp thừa kế (Derived class) đều có thể sử dụng mà không cần phải viết lại.

-          Thuận tiện trong bảo trì và phát triển ứng dụng : Gỉa sử sau này, khi cần phải thêm các đặc tính về màu sơn, hãng sản xuất cho các loại phương tiện giao thông để quản lý thì ta chỉ cần thêm 2 đặc tính này vào lớp cơ sở (Base class) của chúng là tất cả các lớp thừa kế (Derived class) đều có những đặc tính này (Thay vì chúng ta phải khai báo lại cho từng lớp con)

Cú pháp để triển khai việc thừa kế đối với các lớp trong C Sharp được mô tả như sau:

class <lớp_con> :  <lớp_cơ_sở>

Ví dụ:

Trong ví dụ này, tôi mô tả thừa kế thông qua lớp xeDap cho lý giải về các phương tiện giao thông ở trên

public class  ptGiaoThong{
   private int  _taiTrong;        
//Khả năng chở hàng của phương tiện giao thông
   private string _loaiDongCo;          //Loại động cơ của phương tiện giao thông
   private string _bienSo;        
//Mô tả biển số xe
   public ptGiaoThong(){
//Constructor không tham số thay cho Default constructor
         this._taiTro = 100;
        
this._loaiDongCo = “Hộp số”;
        
this._bienSo = “”;
   }
   public void  diChuyen(){       
//Phương thức mô tả cho khả năng di chuyển
         console.WriteLine(“Đang di chuyển từ điểm A ... đến điểm B”);
   }
   public void thongTinXe(){      
//Phương thức in ra thông tin của xe
         console.WriteLine(“Tải trọng của xe : {0} tấn”,this._taiTrong);
         console.WriteLine(“
Xe sử dụng động cơ : {0}”,this._loaiDongCo);
         console.WriteLine(“
Biển số đăng ký : {0}”, this._bienSo);
   }
}

public
class xeDap : ptGiaoThong{   //Khai báo lớp xeDap thừa kế từ lớp ptGiaoThong
   static void  Main( string[] args){
         xeDap cx = new xeDap();
         cx.thongTinXe();
         cx.diChuyen();
   }
}

Diễn giải : Như bạn thấy, để tạo ra lớp xeDap (Derived class) thừa kế từ lớp ptGiaoThong (Base class) chúng ta chỉ cần sử dụng khai báo class  xeDap : ptGiaoThong giống như cú pháp là đủ. Tiếp theo, trong hàm Main của lớp xeDap, chúng ta tạo ra đối tượng cx và đương nhiên đối tượng này cũng có và có thể sử dụng các phương thức đã được định nghĩa trong lớp ptGiaoThong mặc dù trong lớp xeDap chẳng hề viết 1 dòng lệnh nào cả vì lớp xeDap được thừa kế lại các tính chất cũng như các phương thức đã có trong lớp ptGiaoThong

Tương tự như thế, bạn cũng có thể tạo ra các lớp đại diện cho các phương tiện giao thông khác như xeTai, xeCon, xeMay cho chương trình của mình giống như cách trên. Tuy nhiên với cách sử dụng access modifier như trong đoạn code của tôi cùng với các phương thức sẵn có ở trên, có thể bạn sẽ cảm thấy hơi “mù mờ” ở chỗ cả 4 lớp xeTai, xeCon, xeMay và xeDap chẳng lẽ khi tạo ra đều giống nhau như thế thì đâu có ý nghĩa gì, ít ra chúng phải có những khác biệt và những đặc thù riêng chứ !. Đúng thế, chúng ta thừa kế từ lớp ptGiaoThong là để các lớp con như xeDap, xeMay, xeTai, xeCon có những đặc điểm chung giống nhau, còn tại mỗi lớp con (Derived class) ta đều có thể xây dựng thêm những đặc điểm riêng cho từng loại đối tượng của mỗi lớp.

Từ khóa truy cập “Protected” trong thừa kế

Từ khóa “protected” dùng để khai báo mức truy cập đối với dữ liệu thành viên của lớp theo ý nghĩa chỉ cho phép các lớp thừa kế (Derived class) từ lớp cơ sở (Base class) được quyền tác động đến những thành viên thuộc dạng này còn những lớp không thuộc cùng phả hệ thì không được phép.

Nói cách khác, đối với những biến (Variable), hàm hoặc phương thức (Method)  được khai báo truy cập bởi từ khóa protected thì tại lớp thừa kế (Derived class), chúng ta có thể truy cập đến những thành viên thuộc dạng này giống như cách truy xuất đối với những thành viên được khai báo public còn trong những lớp không phải là lớp thừa kế từ lớp cơ sở thì không được phép(trong tình huống này thì khai báo protected mang ý nghĩa giống như private). Hãy quan sát bảng mô tả sau

 

Base class : ptGiaoThong

 

 

protected  int  _taiTrong;

protected string _loaiDongCo;

protected string _bienSo;

Private   string _ngaySX;

protected void diChuyen(){…}

 

 

 

 

class  xeDap: ptGiaoThong

 

class  xeMay: ptGiaoThong

 

class nguoiDan

Được phép truy xuất đến các biến và phương thức: _taiTrong, _loaiDongCo, _bienSo, diChuyen()

Không được phép truy xuất biến: _ngaySX

Được phép truy xuất đến các biến và phương thức: _taiTrong, _loaiDongCo, _bienSo, diChuyen()

Không được phép truy xuất biến: _ngaySX

Không được phép truy xuất: _taiTrong, _loaiDongCo, _bienSo, diChuyen(), _ngaySX

Ý nghĩa của từ khóa “base” trong thừa kế

Đây là từ khóa cho phép bạn có thể sử dụng để truy xuất đến các biến, các phương thức có trong lớp cơ sở (Base class) mà lớp hiện tại đã thừa kế từ nó. Cú pháp cho phép truy cập đến thành viên trong lớp cơ sở (Base class) từ lớp thừa kế được mô tả như sau

class <baseName>{
     …
<access_modifier>  <return_type> <Base_Method> (list_of_argument){
     }
}
class <derivedName> : <baseName>{
         base.<Base_Method>( … );  
//--- Truy xuất đến phương thức của lớp cơ sở
}

Ví dụ: trong ví dụ sau chúng ta sẽ ứng dụng từ khóa truy cập protected và base để xây dựng thêm các lớp xeMay và  xeTai thừa kế từ lớp ptGiaoThong

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ptGiaoThong
{
    //-------- Xây dựng lớp cơ sở của các loại phương tiện giao thông
    public class  ptGiaoThong{
        protected int  _taiTrong;      
//Khả năng chở hàng của phương tiện GT
        protected string _loaiDongCo;   //Loại động cơ của phương tiện giao thông
        protected string _bienSo;       //Mô tả biển số xe
        public ptGiaoThong(){  //Constructor không tham số thay cho Default constructor
              this._taiTrong = 100;
              this._loaiDongCo = "Hop so";
              this._bienSo = "";
        }
        public void  diChuyen
(){     //Phương thức mô tả cho khả năng di chuyển
              Console.WriteLine("Dang di chuyen tu diem A ... den diem B");
        }
        public void thongTinXe(){      
//Phương thức in ra thông tin của xe
              Console.WriteLine("Tai trong cua xe : {0} Kg",this._taiTrong);
              Console.WriteLine("Xe su dung dong cơ : {0}",this._loaiDongCo);
              Console.WriteLine("Bien so dang ky : {0}", this._bienSo);
        }
    }
    //-------- Xây dựng lớp xeMay thừa kế từ lớp ptGiaoThong
    public class  xeMay : ptGiaoThong
        public xeMay(){
              base._taiTrong = 250;
              base._loaiDongCo = "Xang, 2 thi";
              base._bienSo = "50F1-3456";
        }
        new void diChuyen(){
              base.diChuyen();    //Gọi phương thức di chuyển từ lớp cơ sở
            Console.WriteLine("Bang phương tien Xe may");
        }
    }
    //-------- Xây dựng lớp xeTai thừa kế từ lớp ptGiaoThong
    public class  xeTai : ptGiaoThong
        public xeTai(){
              base._taiTrong = 5000;
              base._loaiDongCo = "Diesel";
              base._bienSo = "54M3-6789";
        }
        new void diChuyen()
        {
              base.diChuyen();         
//Gọi phương thức di chuyển từ lớp cơ sở
            Console.WriteLine("Bang phương tien Xe tai");
        }
    }
    //-------- Xây dựng lớp xeDap thừa kế từ lớp ptGiaoThong
    public class  xeDap : ptGiaoThong{      }
    class Program
    {
        static void Main(string[] args)
        {
             //--- Tạo 1 đối tượng của lớp xeDap trong chương trình ---
            xeDap xd = new xeDap();
            Console.WriteLine("Doi tuong xe dap trong chuong trinh");
            xd.thongTinXe();
            xd.diChuyen();
            Console.WriteLine("====================================");
             //--- Tạo 1 đối tượng của lớp xeMay trong chương trình ---
            xeMay xm = new xeMay();
            Console.WriteLine("Doi tuong xe May trong chương trinh");
            xm.thongTinXe();
            xm.diChuyen();
            Console.WriteLine("====================================");
             //--- Tạo 1 đối tượng của lớp xeTai trong chương trình ---
            xeTai xt = new xeTai();
            Console.WriteLine("Doi tuong xe tai trong chuong trinh");
            xt.thongTinXe();
            xt.
diChuyen();
            Console.ReadLine();
        }
    }
}

Kết quả thi hành chương trình

Ghi chú: Như chúng ta đã biết, từ khóa new thường được sử dụng để tạo ra 1 thể hiện của lớp trong chương trình. Tuy nhiên, trong các lớp thừa kế, bạn có thể sử dụng từ khóa new như là 1 access modifier để định nghĩa lại 1 phương thức đã có ở lớp cơ sở; trong ví dụ trên, chúng ta dùng từ khóa new để định nghĩa lại phương thức diChuyen() trong các lớp xeMay và xeTai (phương thức này đã được định nghĩa trong lớp ptGiaoThong) đồng thời trong phương thức của các lớp thừa kế, chúng ta đã truy xuất đến phương thức diChuyen() có trong lớp cơ sở thông qua từ khóa base.

Hãy nhớ !. Trong lớp thừa kế, chúng ta có thể sử dụng từ khóa new như là access modifier để định nghĩa lại các thành phần của lớp cơ sở (Base class) tại lớp thừa kế (Derived class)

Cơ chế gọi thực thi constructor trong thừa kế

Nhưn đã đề cập trong bài “Xây dựng lớp và phương thức”, Constructor (còn gọi là hàm khởi tạo) là một loại phương thức đặc biệt, nó sẽ tự động thi hành khi đối tượng (Object) của lớp được tạo ra. C Sharp không cho phép thừa kế Constructor từ hớp cơ sở (Base class) giống như các phương thức thông thường khác. Chính vì lẽ đó, tại constructor của lớp thừa kế (Derived class), nếu bạn muốn thực thi lại các lệnh trong trong Constructor nào của lớp cơ sở thì bắt buộc bạn phải  gọi constructor tương ứng thông qua từ khóa base tại dòng khai báo constructor của lớp thừa kế theo cách như sau

public deriveClassConstructor(): base(<tham_số>){
        …
}

Ví dụ : Giả sử theo thiết kế như mô tả của hình minh họa dưới đây, chúng ta xây dựng 1 lớp cơ sở có tên là nguoi, sau đó xây dựng lớp chienBinh  và nongDan thừa kế từ lớp này.

Trong constuctor của lớp chienBinh, và nongDan tôi sẽ khai báo để gọi thực thi constructor của lớp cơ sở như sau.

public class  nguoi{
    protected int _tay;      
//Số tay của 1 người
    protected int _chan;      //Số chân của 1 ngươi
    protected string _mauDa;  //Màu da của người
   
protected string _thucAn; //Thức ăn
    public nguoi(){  //Constructor không tham số thay cho Default constructor
        this._tay = 2;
        this._chan = 2;
        this._mauDa = "Vàng";
        this._thucAn = “Cơm”;
    }
   
//Constructor 2 tham số của lớp nguoi quy định màu da, thực phẩm chính
    public nguoi(string daMau, string ta){
        this._tay = 2;
        this._chan = 2;
        this._mauDa = daMau;
        this._thucAn = ta;
    }
   
//Constructor 4 tham số của lớp nguoi
    public nguoi(int soTay, int soChan, string daMau, string ta){
        this._tay = soTay;
        this._chan = soChan;
        this._mauDa = daMau;
        this._thucAn = ta;
    }
    public void  diChuyen
(){         //Phương thức mô tả cho khả năng di chuyển
        Console.WriteLine("Nguoi dang di bo");
    }
}
//-------- Xây dựng lớp chienBinh thừa kế từ lớp nguoi--------
public class  chienBinh : nguoi
    int _noiLuc;
    int _khangDoc;
    string _binhKhi;
    string _aoGiap;
   
//--- Gọi constructor của lớp cơ sở khi định nghĩa constructor ở lớp chienBinh
    public chienBinh() : base(“Da trắng”, “Bánh mỳ”)
    {
        base._tay = 1;
    }
    new void diChuyen(){
        base.diChuyen();   
//Gọi phương thức di chuyển từ lớp cơ sở
           Console.WriteLine("Chiến binh đang phi thân");
    }
}
//-------- Xây dựng lớp nongDan thừa kế từ lớp nguoi ---------
public class  nongDan : nguoi{
    int _gioLaoDong;
    int _kyNang;
    string _nongCu;
   
//--- Gọi constructor của lớp cơ sở khi định nghĩa constructor ở lớp nongDan
    public nongDan() : base(2,2,“Da vàng”, “Khoai nướng”)
    {
    }
    new void diChuyen(){
        base.diChuyen();   
//Gọi phương thức di chuyển từ lớp cơ sở
           Console.WriteLine("Nông dân ra đồng làm việc");
    }
}

Như bạn thấy, khi xây dựng hàm khởi tạo của lớp chienBinh, tôi đã gọi thực thi constructor 2 tham số của lớp nguoi như sau

    public chienBinh() : base(“Da trắng”, “Bánh mỳ”)
    {
        base._tay = 1;
    }

Và cũng tương tự, tại constructor của lớp nongDan, ta đã gọi constructor 4 tham số của lớp nguoi như thế này

//--- Gọi constructor của lớp cơ sở khi định nghĩa constructor ở lớp nongDan
    public nongDan() : base(2,2,“Da vàng”, “Khoai nướng”)
    {
    }

Như vậy, khi đối tượng của lớp thừa kế được tạo ra thì cơ chế thực thi constructor sẽ được thực hiện theo thứ tự như sau

-          Constructor của lớp cơ sở (base class) được gọi đầu tiên

-          Constructor của lớp thừa kế (Derived class) sẽ thực thi sau

Hình vẽ minh họa cơ chế thực thi constructor trong thừa kế như sau

Overriding phương thức trong thừa kế

Overriding là một tính năng có ở hầu hết các ngôn ngữ lập trình hướng đối tượng. Trong C Sharp cũng thế, tính năng này cho phép lập trình viên có thể định nghĩa lại phương thức có trong lớp cơ sở tại lớp thừa kế. Nói cách khác, với cùng 1 phương thức đã định nghĩa trong lớp cơ sở, chúng ta có thể sử dụng lại tại lớp thừa kế bằng cách thay đổi “hành vi” của nó để phù hợp hơn đối với đối tượng của lớp thừa kế.

Tính năng này đáp ứng cho bài toán : “Làm sao để tái sử dụng mã  lệnh của lớp cơ sở (Base class) tại lớp thừa kế (Derived class) trong khi vẫn bảo toàn được những ưu điểm của việc thừa kế


Hình minh họa cho việc phân biệt tính năng Overriding phương thức

Lưu ý:

Khi thực hiện kỹ thuật Overriding phương thức tại lớp thừa kế, bạn cần phải quan tâm đến tầm vực (Access modifier) của phương thức. Có nghĩa là khi 1 phương thức được khai báo với “access modifier” là private ở lớp cơ sở thì tại lớp thừa kế, bạn không thể override nó với “access modifier” là public.

Trong C Sharp, để có thể sử dụng kỹ thuật overriding cho 1 phương thức thì tại lớp cơ sở, phương thức đó phải được khai báo là virtual. Một phương thức được khai báo với từ khóa virtual ờ phía trước thì phương thức đó được gọi là phương thức ảo (virtual method) và nếu ở lớp cơ sở có chứa phương thức ảo thì tại lớp thừa kế, bạn bắt buộc phải định nghĩa lại phương thức đó thông qua việc sử dụng từ khóa override ở phía trước

Vi du: Chúng ta sẽ thực hiện lại ví dụ trước đó nhưng sử dụng thêm kỹ thuật overriding method cho phương thức diChuyen như sau:

public class  nguoi{
    protected int _tay;      
//Số tay của 1 người
    protected int _chan;      //Số chân của 1 ngươi
    protected string _mauDa;  //Màu da của người
    protected string _thucAn; //Thức ăn
    public nguoi(){  //Constructor không tham số thay cho Default constructor
        this._tay = 2;
        this._chan = 2;
        this._mauDa = "Vàng";
        this._thucAn = “Cơm”;
    }
   
//Constructor 2 tham số của lớp nguoi quy định màu da, thực phẩm chính
    public nguoi(string daMau, string ta){
        this._tay = 2;
        this._chan = 2;
        this._mauDa = daMau;
        this._thucAn = ta;
    }
   
//Constructor 4 tham số của lớp nguoi
    public nguoi(int soTay, int soChan, string daMau, string ta){
        this._tay = soTay;
        this._chan = soChan;
        this._mauDa = daMau;
        this._thucAn = ta;
    }
    //--- Khai báo virtual method để bắt buộc phải override tại lớp thừa kế
    public virtual void  diChuyen
(){       //Phương thức mô tả cho khả năng di chuyển
        Console.WriteLine("Nguoi dang di bo");
    }
}
//-------- Xây dựng lớp chienBinh thừa kế từ lớp nguoi--------
public class  chienBinh : nguoi
    int _noiLuc, _khangDoc;
    string _binhKhi, _aoGiap;
   
//--- Gọi constructor của lớp cơ sở khi định nghĩa constructor ở lớp chienBinh
    public chienBinh() : base(“Da trắng”, “Bánh mỳ”)
    {
        base._tay = 1;
    }
    //--- Sử dụng kỹ thuật overriding cho hàm diChuyen tại lớp chienBinh
    public override void diChuyen(){
        base.diChuyen();   
//Gọi phương thức di chuyển từ lớp cơ sở
        Console.WriteLine("Chiến binh đang phi thân");
    }
}
//-------- Xây dựng lớp nongDan thừa kế từ lớp nguoi ---------
public class  nongDan : nguoi{
    int _gioLaoDong, _kyNang;
    string _nongCu;
   
//--- Gọi constructor của lớp cơ sở khi định nghĩa constructor ở lớp nongDan
    public nongDan() : base(2,2,“Da vàng”, “Khoai nướng”)
    {
    }
    //--- Sử dụng kỹ thuật overriding cho hàm diChuyen tại lớp nongDan
    public override void diChuyen(){
        base.diChuyen();   
//Gọi phương thức di chuyển từ lớp cơ sở
        Console.WriteLine("Nông dân ra đồng làm việc");
    }
}

Ghi chú:

-          Nếu bạn override 1 phương thức không phải là virtual ở lớp cơ sở thì khi biên dịch, C Sharp sẽ đưa ra thông báo lỗi bởi vì điều này là không hợp lệ. Tuy nhiên, nếu 1 phương thức được khai báo là virtual tại lớp cơ sở (Base class) nhưng trong lớp thừa kế (Derived class), bạn “quên” không override lại thì khi biên dịch, C sharp chỉ đưa ra cảnh báo để nhắc nhở và chương trình vẫn được biên dịch thành công.

-          Khi khai báo phương thức của 1 lớp, các từ khóa : new, static, virtual không được phép sử dụng chung với override.

Sealed class

Trong C Sharp, từ khóa sealed dùng để ngăn chặn 1 lớp trở thành lớp cơ sở của 1 lớp khác. Nói cách khác, nếu một lớp khi được định nghĩa với từ khóa sealed ở phía trước thì lớp đó sẽ không cho phép thừa kế từ nó.

Hãy quan sát đoạn code dưới đây

public class  dongVat{
    protected int _phanNhom;       
//Số tay của 1 người
    protected string _thucAn;       //Số chân của 1 ngươi
    public dongVat(){  //Constructor không tham số thay cho Default constructor
        this._phanNhom = 0;
        this._thucAan = “”;
    }
    
}
//-------- Xây dựng lớp chienBinh thừa kế từ lớp nguoi--------
public sealed class  ca{
    private bool _vay;
    private string _duoi;
    public ca()
    {
        this._vay = true;
        this._duoi = “nhon”;
    }
}
//-------- Xây dựng lớp caChep thừa kế từ lớp ca ---------
public class  caChep : ca{
   
//--- Gọi constructor của lớp cơ sở khi định nghĩa constructor ở lớp nongDan
    public caChep() : base()
    {  }
}

Đoạn code này sẽ bị báo lỗi khi biên dịch chương trình vì chúng ta đã thừa kế lớp caChep từ lớp ca trong khi lop ca đã được khai báo là sealed class.

Lý do của việc “ngăn chặn thừa kế” thông qua sealed class

Giả sử trong chương trình của mình, bạn triển khai 1 lớp đặc biệt để đọc ra các thông số quan trọng của hệ thống ứng dụng và bạn không hề mong muốn người khác có thể xây dựng một lớp mới thừa kế từ lớp này (vì 1 số lý do khách quan đối với thông tin khai thác được từ lớp này, vì lý do bảo mật, …) và như thế bạn sẽ định nghĩa lớp đặc biệt đó dưới dạng sealed class.

Điều này là dễ hiểu bởi lẽ trên thực tế, có 1 số lớp cung cấp thông tin hệ thống mà .Net cung cấp cũng không cho phép bạn thừa kế từ nó mà chỉ cho phép bạn sử dụng để khai thác thông tin từ nó mà thôi. Hãy liên tưởng ! với những lớp quan trọng, nếu bạn được phép thừa kế từ nó và cố tình override một số phương thức để “xuyên tạc” hay làm “méo mó” thông tin khai thác được từ lớp ban đầu để đánh lừa người dùng, hệ điều hành, … thì hậu quả có lẽ sẽ rất nghiêm trọng.

sealed class nên sử dụng trong những tình huống nào ?

Người ta chỉ khai báo 1 lớp là “sealed class” khi gặp phải những vấn đề sau

-          Nếu việc override phương thức tại lớp thừa kế có thể làm “méo mó” ý nghĩa sử dụng của các thành viên thuộc lớp cơ sở so với thiết kế ban đầu

-          Người xây dựng lớp cơ sở (tác giả) không muốn người tái sử dụng mã lệnh của mình làm thay đổi ý nghĩa ban đầu (mục tiêu thiết kế) của nó.

Lưu ý:

Một lớp được khai báo là sealed thì sẽ không có thành viên nào được khai báo với access modifier là protected bởi vì từ khóa này chỉ có ý nghĩa khi truy xuất thành viên của lớp cơ sở từ lớp thừa kế, trong khi đó sealed class lại không cho phép thừa kế.

Nếu bạn cố tình khai báo 1 thành viên trong sealed class là protected thì khi biên dịch, C Sharp sẽ cảnh báo về điều này !.

Tính đa hình trong C Sharp

Đa hình có thể hiểu là “có nhiều hình thái khác nhau”, trong lập trình hướng đối tượng, đa hình (polymophism) là thuật ngữ nói đến khả năng hành xử khác nhau trong những tình huống khác nhau đối với 1 thể hiện của lớp trong chương trình.

Hãy liên tưởng đến việc bạn xây dựng ứng dụng là 1 game đánh trận, trong đó các nhân vật thuộc lớp chiến binh trong chương trình có khả năng tấn công đối phương. Phương thức tấn công của lớp chiến binh có 3 tình huống có thể sử dụng tùy theo trạng thái của đối tượng

-          Tấn công bằng quyền khi không có vũ khí

Street Fighter x Tekken sẽ chinh phục mọi cao thủ!, Tin game, game, game doi khang, game hanh dong, Street Fighter, Tekken, Street Fighter x Tekken, nam co, capcom

-          Tấn công khi sử dụng cung tên

-          Tấn công khi vũ khí sử dụng là kiếm

Tera Online thu hàng tỷ tiền dù game chưa mở cửa, Tin game, game, game online, tin game, tin tức game, game offline, game thủ, tiền tỷ.

Cũng là tấn công nhưng tùy theo tình huống mà phương thức tấn công sẽ có những thể hiện khác nhau, điều này chính là tính đa hình trong lập trình hướng đối tượng.

Làm sao để áp dụng đa hình trong C Sharp ?

Bạn có thể thực hiện đa hình trong C Sharp thông qua kỹ thuật overloading  overriding đối với phương thức của lớp. Có nghĩa là bạn có thể tạo ra nhiều phương thức có cùng tên trong một lớp hoặc trong các lớp khác nhau với mã lệnh để xử lý trong phương thức cũng như đặc điểm của tham số sử dụng cho phương thức hoàn toàn khác nhau. Như chúng ta đã biết, nếu các phương thức  cùng tên  nhưng có khác biệt về tham số, tồn tại trong cùng một lớp chính là kỹ thuật Overloading dành cho phương thức đã đề cập ở phần trước. Điều này có nghĩa là lập trình viên sẽ tạo ra cùng một phương thức để thực hiện các chức năng tương tự dựa trên các giá trị đầy vào khác nhau.

Phương pháp thừa kế từ lớp cơ sở (Base class) đồng thời định nghĩa lại phương thức tại lớp thừa kế (Derived class) chính là kỹ thuật overriden mà chúng ta vừa đề cập ở trên. Trong tình huống này, chính những thay đổi của hàm tại lớp dẫn xuất sẽ tạo nên hình thái mới cho đối tượng

Ví dụ: Chúng ta sẽ xây dựng 1 lớp hình tròn và trang bị cho lớp này 1 phương thức phục vụ cho yêu cầu tính diện tích. Sau đó ta sẽ định nghĩa 1 lớp hình trụ thừa kế từ lớp hình tròn đồng thời override lại phương thức tính diện tích tại lớp hình trụ. Như vậy cũng với phương thức tính diện tích, khi đối tượng là thể hiện của lớp cơ sở (lớp hình tròn) thì phương thức này sẽ sử dụng công thức tính pi*r*r nhưng nếu là đối tượng của lớp thừa kế (lớp hình trụ) thì phương thức này sẽ sử dụng công thức tính 2*pi*r*(h + r). mã lệnh của ví dụ này được viết như sau

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace tinhToanCacHinh
{
    class hinhTron{
        protected const double pi = 3.14;
        protected double r;
        /// <summary>
        /// Phương thức khởi tạo 1 tham số của lớp hình tròn
        /// có nhiệm vụ khởi gán giá trị cho bán kính r của lớp
        /// </summary>
        public hinhTron(double banKinh){
              r = banKinh;
        }
        /// <summary>
        /// Định nghĩa 1 virtual method để tính diện tích và yêu
        /// cầu phải định nghĩa lại phương thức này tại lớp thừa kế
        /// </summary>
        /// <returns></returns>
        public virtual double dienTich(){
              return  pi * r * r;
        }
    }
    class hinhTru : hinhTron{
        public double h;
        /// <summary>
        /// Phương thức khởi tạo 2 tham số của lớp hình trụ
        /// gọi thi hành phương thức khởi tạo của lớp cơ sở trước
        /// khi thực thi các lệnh có bên trong nó
        /// </summary>
        public hinhTru(double banKinh, double chieuCao): base(banKinh){
              h = chieuCao;
        }
        /// <summary>
        /// Định nghĩa lại phương thức dienTich
        /// thừa kế được từ lớp hinhTron
        /// </summary>
        /// <returns></returns>
        public override double dienTich(){
              return  2 * pi * r * (h + r);
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            // Tạo 1 thể hiện của lớp hinhTron với bán kính = 2
            hinhTron h1 = new hinhTron(2);
            Console.WriteLine("Diện tích hình tròn = {0}", h1.dienTich());
            // Tạo 1 thể hiện của lớp hinhTru với bán kính = 2, chiều cao =5
            hinhTru h2 = new hinhTru(2,5);
            Console.WriteLine("Diện tích xung quanh hình trụ = {0}",                                    h2.dienTich());
            Console.ReadLine();
        }
    }
}

Qua ví dụ này, bạn thấy cũng là phương thức dienTich nhưng với đối tượng h1 của lớp hinhTron, nó sẽ thực hiện công thức tính diện tích cho hình tròn, cũng vẫn phương thức này, kho gọi thông qua đối tượng của lớp hinhTru, nó sẽ thực hiện công thức tính diện tích xung quanh cho hình trụ (với đối tượng của các lớp khác nhau, sẽ có hành vi khác nhau) đây chính là tính đa hình (Polymorphism) mà chúng ta đã đề cập trong phần cuối của bài viết này.

Khi thi hành ứng dụng trên, bạn sẽ nhận được kết quả như hình sau

www.bodua.com

Comments