Abstract class và Interface trong C#

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ụ.
Đặ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 lớp 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.
       // 'access modifier' của phương thức này là: public
       // (access modifier: Độ truy cập).
       public abstract void DoSomething();

       // 'access modifier' của phương thức này là: protected
       protected abstract String DoNothing();

      
       protected abstract void Todo();
   }

   // Đây là một class trừu tượng.
   // Nó được khai báo là trừu tượng, mặc dù nó không có phương thức 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 với từ khóa 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
{
    // Lớp có ít nhất một phương thức trừu tượng, 
    // phải được khai báo là trừu tượng (abstract).
    public abstract class AbstractJob
    {

        public AbstractJob()
        {

        }

        // Đây là một phương thức trừu tượng,
        // Nó không có thân (body).
        // Phương thức 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ó thân (body).
        public abstract void DoJob();

        // Đây là một phương thức thông thường (Không trừu tượ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()
        {
        }

        // Thực hiện phương thức trừu tượng khai báo tại lớp cha.
        // Nó phải có thân (body).
        // (Cần phải có từ khóa 'override').
        public override void DoJob()
        {
            Console.WriteLine("Coding Java...");
        }

        // Thực hiện phương thức trừu tượng của lớp cha.
        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()
        {
        }

        // Thực hiện phương thức trừu tượng khai báo tại lớp cha.
        // Nó phải có thân (body).
        // (Cần phải có từ khóa 'override').
        public override void DoJob()
        {
            Console.WriteLine("Coding CSharp...");
        }

        // Thực hiện phương thức trừu tượng của lớp cha.
        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)
    // Lớp cha của nó (AbstractJob) có 2 phương thức trừu tượng.
    // Lớp này mới chỉ thực hiện 1 phương thức của lớp cha.
    // Vì vậy nó bắt buộc phải được khai báo là 'abstract'.
    public abstract class ManualJob : AbstractJob
    {

        public ManualJob()
        {

        }

        // Thực hiện phương thức trừu tượng khai báo tại lớp 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
{

    // Lớp này thừa kế từ lớp trừu tượng ManualJob.
    // BuildHouse không khai báo 'abstract'
    // Vì vậy nó cần thực hiện tất cả các phương thức trừu tượng còn lại.
    public class BuildHouse : ManualJob
    {

        public BuildHouse()
        {

        }

        // Triển khai phương thức trừu tượng của lớp cha.   
        // (Cần phải sử dụng 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)
        {
            // Tạo một đối tượng AbstractJob từ Constructor của lớp JavaCoding.
            AbstractJob job1 = new JavaCoding();

            // Gọi phương thức DoJob().
            job1.DoJob();

            // Phương thức GetJobName() là trừu tượng trong lớp AbstractJob
            // Nhưng nó đã được triển khai tại một lớp con nào đó.
            // Vì vậy bạn có thể gọi nó.
            String jobName = job1.GetJobName();

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


            // Tạo một đối tượng AbstractJob từ Constructor của lớp CSharpCoding.
            AbstractJob job2 = new CSharpCoding();

            // Gọi phương thức DoJob().
            job2.DoJob();

            String jobName2 = job2.GetJobName();

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

            // Tạo đối tượng AbstractJob từ Constructor của lớp 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- Tổng quan về Interface

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

// Trong trường hợp bạn không chỉ rõ lớp này mở rộng từ lớp nào.
// CSharp tự hiểu rằng lớp này mở rộng từ lớp Object.
public class B
{

}

// Cách khai báo lớp dưới đâ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 lớp chỉ có thể mở rộng từ duy nhất một lớp khác.
// 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).

5- 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 (compile) 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ứa 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 'access modifier' của nó sẽ 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, 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
    {

        // Các phương thức 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. 
        void Back();

        // Trả về 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(); 
    }

}

6- Class thi hành Interface

Khi một class thi hành một Interface, bạn phải thực hiện 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 (Một lớp mô phỏng lớp động vật)
    // Nó mở rộng từ lớp Object (Mặc dù không ghi rõ ràng).
    // Và khai báo thực hiện (implements) interface CanMove.
    // Interface CanMove có 3 phương thức trừu tượng.
    // Lớp này mới thực hiện 1 phương thức của CanMove.
    // Vì vậy nó bắt buộc phải khai báo là 'abstract'.
    // Các phương thức còn lại phải được khai báo lại với 'public abstract'.
    public abstract class Animal : CanMove
    {

        // Thực hiện phương thức Run() của 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 lớp này không thực hiện một phương thức nào đó của Interface 
        // bạn phải viết lại nó như là một phương thức trừu tượng.
        // (Luôn luôn là 'public abstract')
        public abstract void Back(); 

        public abstract int GetVelocity();

    }


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

namespace AbstractClassInterface
{

    // Lớp Cat mở rộng từ lớp Animal và thực hiện 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;
        }

        // Thực hiện 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 ...");
        }

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

        // Thực hiện phương thức của interface CanEat    
        public void Eat()
        {
            Console.WriteLine(name + " cat eat ...");
        }

        // Thực hiện phương thức 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
    {

        // Thực hiện phương thức trừu tượng của lớp Animal.
        // (Phải có từ khóa 'override').
        public override void Back()
        {
            Console.WriteLine("Mouse back ...");
        }

        // Thực hiện phương thức trừu tượng của lớp Animal. 
        public override int GetVelocity()
        {
            return 85;
        }

        // Thực hiện phương thức của interface CanDrink.
        public void Drink()
        {
            Console.WriteLine("Mouse drink ...");
        }

        // Thực hiện 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)
        {

            // 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 (Polymorphism) thể hiện rõ tại đây.
            // CSharp luôn biết kiểu thực tế của một đối tượng.
            // ==> 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 phương thức Drink() (Thừa kế từ CanDrink).
                mouse.Drink();
            }

            Console.ReadLine();
        }
    }

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

Xem thêm các chuyên mục: