Abstract class và Interface trong C#
Công ty Vĩnh Cửu tuyển dụng lập trình viên Java

1- Giới thiệu

Trong tài liệu hướng dẫn này tôi sẽ hướng dẫn về Interface và class trìu tượng (Abstract Class). Đồng thời phân tích sự giống và khác nhau giữa chúng.
Trước hết bạn cần tạo một project có tên AbstractClassInterface để làm việc với các ví dụ.
Sét nó là project mặc định.

2- Class trìu tượng (Abstract Class)

Abstract class (Class trìu tượng). Hãy xem ví dụ về một class như thế:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace AbstractClassInterface
{
   // Một class có ít nhất một phương thức trìu tượng,
   // phải khai báo là trìu tượng (abstract).
   public abstract class ClassA
   {

       // Đây là một method trìu tượng.
       // Nó không có thân hàm.
       // Method này có access modifier là: public
       // (access modifier: Độ truy cập).
       public abstract void DoSomething();

       // Method này có access modifier là protected
       protected abstract String DoNothing();

       
       protected abstract void Todo();
   }

   // Đây là một class trìu tượng.
   // Chủ động khai báo abstract, mặc dù nó không có method trìu tượng nào.
   public abstract class ClassB
   {

   }
}
Đặc điểm của một class trìu tượng là:
  1. Nó được khai báo là abstract.
  2. Nó có thể khai báo 0, 1 hoặc nhiều method trìu tượng bên trong.
  3. Bạn không thể khởi tạo 1 đối tượng trực tiếp từ một class trìu tượng.

3- Ví dụ với class trìu tượng

Hãy xem hình minh họa:
AbstractJob.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace AbstractClassInterface
{
  // Class có ít nhất một phương thức trìu tượng, phải khai báo là abstract.
  public abstract class AbstractJob
  {

      public AbstractJob()
      {

      }

      // Đây là một phương thức trìu tượng,
      // Nó không có nội dung (body)
      // Method này trả về tên của công việc.
      public abstract String GetJobName();

      // Đây là một phương thức trìu tượng.
      // Phương thức không có nội dung.
      public abstract void DoJob();

      // Class trìu tượng vẫn có các phương thức thông thường.
      public void StopJob()
      {
          Console.WriteLine("Stop");
      }
  }
}
JavaCoding.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace AbstractClassInterface
{
  public class JavaCoding : AbstractJob
  {

      public JavaCoding()
      {
      }

      // Method này triển khai method trìu tượng khai báo tại class cha.
      // (Cần phải có từ khóa 'override').
      public override void DoJob()
      {
          Console.WriteLine("Coding Java...");
      }

      // Method này triển khai method trìu tượng khai báo tại class cha.
      // Method này sẽ có thân hàm đầy đủ
      // Method trả về tên của công việc.
      // (Cần phải có từ khóa 'override').
      public override String GetJobName()
      {
          return "Java Coding";
      }

      public void TestExample()
      {
          Console.WriteLine("Testing Example...");
      }
  }
}
CSharpCoding.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace AbstractClassInterface
{
   class CSharpCoding : AbstractJob
   {
       public CSharpCoding()
       {
       }

       // Method này triển khai method trìu tượng khai báo tại class cha.
       // (Cần phải có từ khóa 'override').
       public override void DoJob()
       {
           Console.WriteLine("Coding CSharp...");
       }

       // Method này triển khai method trìu tượng khai báo tại class cha.
       // Method này sẽ có thân hàm đầy đủ
       // Method trả về tên của công việc.
       // (Cần phải có từ khóa 'override').
       public override String GetJobName()
       {
           return "CSharp Coding";
       }

       public void RunningExample()
       {
           Console.WriteLine("Running Example...");
       }
   }
}
ManualJob.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace AbstractClassInterface
{

  // ManualJob - (Mô phỏng một công việc phổ thông)
  // Class cha (AbstractJob) có 2 method trìu tượng.
  // Class này mới chỉ triển khai 1 method trìu tượng của class cha.
  // Vì vậy nó bắt buộc phải khai báo là abstract.
  public abstract class ManualJob : AbstractJob
  {

      public ManualJob()
      {

      }

      // Method này triển khai method trìu tượng khai báo tại class cha
      // (Cần phải có từ khóa 'override').
      public override String GetJobName()
      {
          return "Manual Job";
      }

  }
}
BuildHouse.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace AbstractClassInterface
{

  // Class này thừa kế từ class trìu tượng ManualJob
  // BuildHouse không khai báo abstract
  // Vì vậy nó cần triển khai các method trìu tượng còn lại.
  public class BuildHouse : ManualJob
  {

      public BuildHouse()
      {

      }

      // Triển khai method trìu tượng của class cha.  
      // (Cần phải có từ khóa 'override').
      public override void DoJob()
      {
          Console.WriteLine("Build a House");
      }

  }

}
Ví dụ demo
JobDemo.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace AbstractClassInterface
{
  public class JobDemo
  {

      public static void Main(string[] args)
      {

          // Khởi tạo một đối tượng AbstractJob.
          // Nó khởi tạo từ cấu tử của class JavaCoding.
          AbstractJob job1 = new JavaCoding();

          // Gọi method doJob()
          job1.DoJob();

          // Method getJobName là trìu tượng trong class AbstractJob
          // Nhưng nó đã được triển khai tại một class con nào đó.
          // Vì vậy gọi không vấn đề gì.
          String jobName = job1.GetJobName();

          Console.WriteLine("Job Name 1= " + jobName);



          // Khởi tạo một đối tượng AbstractJob.
          // Nó khởi tạo từ cấu tử của class CSharpCoding.
          AbstractJob job2 = new CSharpCoding();

          // Gọi method doJob()
          job2.DoJob();

          // Method getJobName là trìu tượng trong class AbstractJob
          // Nhưng nó đã được triển khai tại một class con nào đó.
          // Vì vậy gọi không vấn đề gì.
          String jobName2 = job2.GetJobName();

          Console.WriteLine("Job Name 2= " + jobName2);


          // Khởi tạo một đối tượng AbstractJob
          // từ cấu tử của class BuildHouse.
          AbstractJob job3 = new BuildHouse();

          job3.DoJob();

          String jobName3 = job3.GetJobName();

          Console.WriteLine("Job Name 3= " + jobName2);


          Console.ReadLine();
      }
  }
}
Kết quả chạy ví dụ:

4- Interface

Chúng ta biết rằng một class chỉ có thể mở rộng từ một class khác.
// Class B là con của class A, hay nói là B mở rộng từ A
// CSharp chỉ cho phép một class mở rộng từ duy nhất một class khác.
public class B : A  
{
  // ....
}

// Trong trường hợp bạn không viết mở rộng từ một class nào.
// CSharp tự hiểu là nó thừa kế từ class Object.
public class B
{

}

// Cách khai báo class này, và cách trên là tương đương nhau.
public class B : Object
{

}
Nhưng một class có thể mở rộng từ nhiều Interface
// Một class có thể mở rộng từ duy nhất một class
// Nhưng có thể mở rộng từ nhiều Interface.
public class Cat : Animal, CanEat, CanDrink
{

// ....
}

Các đặc điểm của interface trong CSharp.

  1. Interface có modifier là public hoặc internal, nếu không ghi rõ mặc định là internal.
  2. Interface không thể định nghĩa các trường (Field).
  3. Các method của nó đều là method trìu tượng (abstract) và công khai (public), và không có thân hàm. Nhưng khi khai báo phương thức bạn lại không được phép ghi public hoặc abstract.
  4. Interface không có cấu tử (Constructor).

4.1- Cấu trúc của một Interface

Một interface trong CSharp có thể khai báo modifier là public hoặc internal, nếu không khai báo gì mặc định được hiểu là internal. Interface có modifier là public có thể được sử dụng ở mọi nơi, đối với interface có modifier là internal chỉ được sử dụng trong nội bộ Assembly.

Một Assembly là chính là sản phẩm đã biên dịch của mã của bạn, thường là một DLL, nhưng EXE cũng có thể coi là một assembly. Nó là đơn vị nhỏ nhất của việc triển khai cho bất kỳ dự án .NET nào.

Assembly một cách cụ thể chứ mã .NET theo MSIL (Microsoft Intermediate language - Một ngôn ngữ trung gian) sẽ được biên dịch thành mã máy (Native code) ("JITted" - biên dịch bởi các trình biên dịch Just-In-Time) trong lần đầu tiên nó được thực thi trên máy tính,. Đó là mã đã được biên dịch cũng sẽ được lưu trữ trong Assembly và tái sử dụng cho các lần gọi tiếp theo.

NoAccessModifierInterface.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace AbstractClassInterface
{
 
   // Đây là một Interface không khai báo access modifier.
   // Mặc định modifier của nó là 'internal'.
   // Nó chỉ được dùng trong nội bộ một Assembly.
    interface NoAccessModifierInterface
   {

   }

}
Các phương thức trong Interface đều là công khai (public) và trìu tượng (abstract). Nó không có thân hàm, nhưng bạn không được phép viết public hoặc abstract khi định nghĩa phương thức.
CanMove.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace AbstractClassInterface
{

 // Interface này định nghĩa những thứ có khả năng di chuyển.
 public interface CanMove
 {

     // (Chạy)
     // Các method trong Interface đều là công khai và trìu tượng (public abstract)
     // (Nhưng bạn không được phép viết public hoặc abstract ở đây)
     void Run();

     // (Quay trở lại)
     // Cho dù không viết rõ public abstract thì CSharp luôn hiểu là vậy.
     void Back();

     // (Lấy ra vận tốc chạy).
     int GetVelocity();

 }

}
CanDrink.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace AbstractClassInterface
{

 // Interface này định nghĩa những thứ có khả năng biết uống.
 public interface CanDrink
 {

       void Drink();

 }

}
CanEat.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace AbstractClassInterface
{

 // Interface này định nghĩa thứ có khả năng biết ăn.
 public interface CanEat
 {

       void Eat();
 }

}

4.2- Class thi hành Interface

Khi một class thi hành một Interface, bạn phải triển khai hoặc khai báo lại toàn bộ các phương thức có trong Interface đó.
  1. Nếu bạn triển khai một phương thức nào đó của interface, bạn phải viết nội dung cho phương thức, khai báo phương thức là public.
  2. Nếu bạn không triển khai một phương thức nào đó của interface, bạn phải khai báo lại nó trong class với từ khóa 'public abstract' và không được viết nội dung của phương thức.
Hãy xem ví dụ, class Animal thi hành interface CanMove.
Animal.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace AbstractClassInterface
{

   // Animal (Class mô phỏng lớp động vật)
   // Nó mở rộng từ class Object (Mặc dù không ghi rõ).
   // Và khai báo thi hành (hoặc gọi là thừa kế) interface CanMove.
   // Interface CanMove có 3 method trìu tượng.
   // Class này mới triển khai 1 method
   // Vì vậy nó bắt buộc phải khai báo abstract
   // Các method còn lại phải được khai báo lại với 'public abstract'.
   public abstract class Animal : CanMove
   {

       // Triển khai method Run() từ interface CanMove.
       // Bạn phải viết nội dung của phương thức.
       // Modifier phải là public.
       public void Run()
       {
           Console.WriteLine("Animal run...");
       }

       // Nếu bạn không triển khai một phương thức nào đó của Interface
       // bạn phải viết lại nó dưới dạng một phương thức trìu tượng.
       // (Luôn luôn là public abstract)
       public abstract void Back();
 

       // Nếu bạn không triển khai một phương thức nào đó của Interface
       // bạn phải viết lại nó dưới dạng một phương thức trìu tượng.
       // (Luôn luôn là public abstract)
       public abstract int GetVelocity();

   }


}
Class Cat thừa kế từ class Animal đồng thời triển khai 2 interface CanDrink, CanEat.
Cat.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace AbstractClassInterface
{

  // Class Cat mở rộng từ class Animal và thi hành 2 interface CanEat, CanDrink.
  public class Cat : Animal, CanEat, CanDrink
  {

      private String name;

      public Cat(String name)
      {
          this.name = name;
      }

      public String getName()
      {
          return this.name;
      }

      // Triển khai phương thức trìu tượng của Animal.
      // (Phải ghi rõ 'override' ).
      public override void Back()
      {
          Console.WriteLine(name + " cat back ...");
      }

      // Triển khai phương thức trìu tượng của Animal
      // (Phải ghi rõ 'override' )
      public override int GetVelocity()
      {
          return 110;
      }

      // Triển khai method của interface CanEat    
      public void Eat()
      {
          Console.WriteLine(name + " cat eat ...");
      }

      // Triển khai method của interface CanDrink
      public void Drink()
      {
          Console.WriteLine(name + " cat drink ...");
      }

  }

}
Mouse.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace AbstractClassInterface
{
  public class Mouse : Animal, CanEat, CanDrink
  {

      // Triển khai phương thức trìu tượng của Animal.
      // (Phải có từ khóa 'override').
      public override void Back()
      {
          Console.WriteLine("Mouse back ...");
      }

      // Triển khai phương thức trìu tượng của Animal.
      // (Phải có từ khóa 'override').
      public override int GetVelocity()
      {
          return 85;
      }

      // Triển khai phương thức của interface CanDrink.
      public void Drink()
      {
          Console.WriteLine("Mouse drink ...");
      }

      // Triển khai phương thức của interface CanEat.
      public void Eat()
      {
          Console.WriteLine("Mouse eat ...");
      }

  }

}
AnimalDemo.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace AbstractClassInterface
{

 public class AnimalDemo
 {

     public static void Main(string[] args)
     {

         // Khởi tạo một đối tượng CanEat
         // Một đối tượng khai báo là CanEat
         // Nhưng thực tế là Cat.
         CanEat canEat1 = new Cat("Tom");

         // Một đối tượng khai báo là CanEat
         // Nhưng thực tế là Mouse.
         CanEat canEat2 = new Mouse();

         // Tính đa hình thể hiện rõ tại đây.
         // CSharp luôn biết một đối tượng là kiểu gì
         // ==> Tom cat eat ...
         canEat1.Eat();

         // ==> Mouse eat ...
         canEat2.Eat();

         bool isCat = canEat1 is Cat;// true

         Console.WriteLine("catEat1 is Cat? " + isCat);

         // Kiểm tra 'canEat2' có phải là chuột hay không?.
         if (canEat2 is Mouse)
         {
             // Ép kiểu
             Mouse mouse = (Mouse)canEat2;
             // Gọi method Drink (Thừa kế từ CanDrink).
             mouse.Drink();
         }

         Console.ReadLine();
     }
 }

}
Kết quả chạy ví dụ: