Trong bài này chúng ta sẽ tìm hiểu:

  • Định nghĩa và thực thi một giao diện
  • Các lớp trừu tượng
  • Các lớp được niêm phong (sealed classes)

Giao diện (interface)

Chúng ta đã tìm hiểu tính năng thừa kế trong C# ở chương VII và chúng ta cũng biết rằng một lớp không được phép thừa kế từ hai hay nhiều lớp. Nhưng với giao diện thì khác.

Giao diện mô tả các tính năng cần thiết cho một lớp nhưng không cho biết các tính năng này thực thi như thế nào. Ví dụ chúng ta có giao diện iATM có hai tính năng là nhập thông tin người dùng và  rút tiền, cách thông tin người dùng được xử lý hay cách thức tiền được chuyển đến người dùng như thế nào sẽ không được đề cập trong giao diện iATM.

Một giao diện không chứa mã hay dữ liệu; nó chỉ xác định các phương thức (kiểu dữ liệu, tên, các tham số) hay các properties và lớp thừa kế giao diện này sẽ chịu trách nhiệm thực thi các phương thức hay properties đó.

Giao diện được định nghĩa bằng từ khoá interface. Các phương thức khai báo trong giao diện giống trong structure hay class nhưng không có có từ khoá chỉ phạm vi truy cập như private, public,v.v. Ví dụ định nghĩa giao diện iATM  như sau:


interface iATM

{

void Nhap_thong_tin();

void Rut_tien();

}

Chúng ta có thể thực thi giao diện này trong lớp thừa kế, ví dụ giao diện iATM có lớp thừa kế là ATMclass như sau:


class ATMclass: iATM

{

  public void Nhap_thong_tin()

  {

    MesssageBox.Show("Vui long nhap thong tin!");

  }

  public void Rut_tien()

  {

    MesssageBox.Show("Vui long nhap so tien!");

  }

}

Một số lưu ý:

– Thông tin về phương thức như tên, kiểu dữ liệu, danh sách tham số trong lớp phải hoàn toàn khớp với giao diện.

– Phạm vi truy cập các phương thức trong lớp thừa kế từ giao diện luôn là public.

Một lớp có thể thừa kế từ một lớp khác đồng thời với một giao diện. Lúc này thứ tự là rất quan trọng và C# ưu tiên lớp cơ sở đứng trước, theo sau là dấu phẩy và giao diện. Ví dụ lớp ATMclass thừa kế từ một lớp gọi là Machine và giao diện iATM như sau:


interface iATM

{

  ...
 
}

class Machine

{

  ...

}

class ATMclass: Machine, iATM

{

  ...

}

Một đối tượng có thể được gán đến một biến có kiểu dữ liệu là giao diện mà kiểu dữ liệu của đối tượng kế thừa. Ví dụ:


interface iATM

{

  void Nhap_thong_tin();

  void Rut_tien();

}

class ATMclass: iATM

{

   public void Nhap_thong_tin()

   {

     MesssageBox.Show("Vui long nhap thong tin!");

   }

   public void Rut_tien()

   {

     MesssageBox.Show("Vui long nhap so tien!");

   }

   public void Hien_thi()

   {

    //...

   }

}

Lưu ý rằng trong phương thức ATMclass, ngoài hai phương thức trong giao diện còn có thêm phương thức Hien_thi(). Lệnh sau là hợp lệ:


ATMclass atm = new ATMclass();

iATM iatm = atm;

Lúc này, biến iatm chỉ có thể gọi được hai phương thức trong giao diện là Nhap_thong_tin()Rut_tien(). iatm không thể nhìn thấy phương thức Hien_thi().

C# cho phép một lớp chỉ thừa kế từ một lớp khác nhưng cho phép thừa kế không hạn chế số lượng các giao diện. Chỉ lưu ý rằng nếu có lớp cơ sở thì lớp này luôn ở vị trí đầu tiên theo sau là các giao diện cách nhau bởi dấu phẩy, như cú pháp:


class A: base_class, interface_1, interface_2,...

{

}

Chính vì có thể thừa kế cùng lúc nhiều giao diện nên đôi khi gây ra những nhầm lẫn cho trình biên dịch nếu như giữa các giao diện có các phương thức giống nhau. Ví dụ giả sử chúng ta có hai giao diện iATM1 và iATM2 có cùng phương thức Rut_Tien() nhưng Rut_Tien của iATM1 dùng để rút USD còn Rut_Tien của iATM2 dùng để rút VND:


interface iATM1

{

  void Rut_tien();// rút USD

}

interface iATM2

{

  void Rut_tien();// rút VND

}

Lớp ATMclass thừa kế hai giao diện:


class ATMclass: iATM1, iATM2

{

  public void Rut_tien()

  {

    MessageBox.SHow("Ban dang rut USD.");

  }

}

Lúc này nếu chúng ta chỉ khai báo phương thức Rut_Tien() như trên thì C# sẽ áp dụng cho cả hai giao diện. Đoạn mã sau sẽ cho ra cùng kết quả:


ATMclass atm = new ATMclass();

iATM1 iatm1 = atm;

iatm1.Rut_Tien(); // Ban dang rut USD.

iATM2 iatm2 = atm;

iatm2.Rut_Tien();// Ban dang rut USD.

Để khắc phục vấn đề này chúng ta cần tập thói quen chỉ rõ giao diện khi khai báo phương thức. Lớp ATMclass trên có thể viết lại:


class ATMclass: iATM1, iATM2

{

  void iATM1.Rut_tien()

  {

    MessageBox.SHow("Ban dang rut USD.");

  }

  void iATM2.Rut_tien()

  {

    MessageBox.SHow("Ban dang rut VND.");

  }

}

Kết quả


ATMclass atm = new ATMclass();

iATM1 iatm1 = atm;

iatm1.Rut_Tien(); // Ban dang rut USD.

iATM2 iatm2 = atm;

iatm2.Rut_Tien();// Ban dang rut VND.

Chú ý khi chỉ rõ giao diện chúng ta không dùng các từ khoá chỉ phạm vi truy cập khi khai báo phương thức trong lớp.

Các lớp trừu tượng (abstract classes)

Cho đến thời điểm này chúng ta đã biết, C# cho phép một lớp chỉ được thừa kế từ duy nhất một lớp khác nhưng có thể được thừa kế bởi nhiều giao diện tại cùng một thời điểm. Ví dụ lớp Person được thừa kế bởi lớp StudentEmployee


class Person

{

...

}

class Student:Person

{

...

}

class Employeee: Person

{

...

}

Trong các trường hợp thực tế, chúng ta  muốn lớp Person chỉ được dùng như là một lớp cơ sở hơn là sử dụng nó, tức là tạo một đối tượng như:


Person p = new Person();

C# cung cấp từ khoá abstract để khai báo một lớp không được phép tạo thể hiện. Ví dụ lớp Person có thể được khai báo như sau:


public abstract class Person

{

…

}

Lớp Person lúc này được gọi là lớp trừu tượng. Nếu chúng ta tạo một thể hiện từ lớp Person sẽ báo lỗi:

Từ khoá abstract cũng có thể được dùng cho phương thức – phương thức trừu tượng. Phương thức trừu tượng chỉ được khai báo trong các lớp trừu tượng và có ý nghĩa tương tự phương thức ảo (virtual method) ngoại trừ nó không có phần thân. Ví dụ phương thức trừu tượng Talk() được khai báo trong lớp trừu tượng Person và khai báo trong các lớp thừa kế StudentEmployee như sau:


public abstract class Person

{

  abstract public void Talk();

}

public class Student:Person

{

  public override void Talk(){

     MessageBox.Show("I am a Person");

  }

}

public class Employee:Person

{

  public override void Talk()

  {

    MessageBox.Show("I am a Employee");

  }

}

Các lớp được niêm phong (sealed classes)

Chúng ta cũng có thể ngăn chặn một lớp có thể được thừa kế bởi các phương thức khác bằng cách biến nó thành lớp được niêm phong(sealed class )với từ khoá sealed.Ví dụ, chúng ta muốn lớp StudentEmployee sẽ không trở thành các lớp cơ sở cho bất cứ lớp nào khác như sau:


public abstract class Person

{

  abstract public void Talk();

}

public sealed class Student:Person

{

  public override void Talk(){

    MessageBox.Show("I am a Person");

  }

}

public sealed class Employee:Person

{

  public override void Talk()

{

MessageBox.Show("I am a Employee");

}

}

Đoạn mã sau sẽ báo lỗi:


public class AnotherEmployee : Employee //lỗi

{

}

Một trong những sức mạnh của tính thừa kế chính là tính đa hình. Với tính đa hình chúng ta có thể dùng một phương thức với những biến thể khác nhau. Ví dụ phương thức Talk() sau:


public abstract class Person

{

  abstract public void Talk();

}

public class Student:Person

{

  public override void Talk(){

    MessageBox.Show("I am a Person");

  }

}

public class Employee:Person

{

  public override void Talk()

  {

    MessageBox.Show("I am a Employee");

  }

}

Phương thức Talk có hai biến thể, một trong lớp Student, một trong lớp Employee. Giả sử chúng ta có một lớp khác, ví dụ AnotherEmployee, thừa kế lớp Employee và chúng ta có một biến thể khác của Talk():


public class AnotherEmployee : Employee

{

  public override void Talk()

  {

    MessageBox.Show("I am a AnotherEmployee");

  }

}

Tuy nhiên, đến một thời điểm nào đó, chúng ta muốn phương thức Talk() sẽ là biến thể cuối cùng và lúc này chúng ta sẽ dùng phương thức được niêm phong với từ khoá sealed. Trong ví dụ trên, chúng ta muốn các biến thể phương thức Talk trong lớp StudentEmployee sẽ là các biến thể cuối cùng, chúng ta viết:


public abstract class Person

{

  abstract public void Talk();

}

public class Student:Person

{

   public sealed override void Talk(){
  
     MessageBox.Show("I am a Person");

   }

}

public class Employee:Person

{

   public sealed override void Talk()

   {

     MessageBox.Show("I am a Employee");

   }

}

Lúc này, nếu chúng ta tạo một biến thể trong lớp AnotherEmployee sẽ báo lỗi:


public class AnotherEmployee : Employee

{

  public override void Talk() // lỗi

  {

     MessageBox.Show("I am a AnotherEmployee");

  }

}

Học C# và WPF >