Thừa kế và đa hình 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

Thừa kế và đa hình - đây là một khái niệm vô cùng quan trọng trong CSharp. Mà bạn bắt buộc phải hiểu nó.

2- Class, đối tượng và cấu tử

Bạn cần hiểu một cách rạch ròi về class, cấu tử và đối tượng trước khi bắt đầu tìm hiểu quan hệ thừa kế trong CSharp. Chúng ta xem class Person, mô tả một con người với các thông tin liên quan.
Person.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace InheritancePolymorphism
{
  class Person
  {
      // Trường name - thông tin tên người
       public String Name;
     
      // Trường bornYear - thông tin năm sinh
       public int BornYear;

      // Nơi sinh
       public String PlaceOfBirth;


      // Cấu tử 3 tham số. Mục đích nhằm để khởi tạo các giá trị cho các trường của Person.
      // Chỉ định rõ tên, năm sinh, nơi sinh.
      public Person(String Name, int BornYear, String PlaceOfBirth)
      {
          this.Name = Name;
          this.BornYear = BornYear;
          this.PlaceOfBirth = PlaceOfBirth;
      }

      // Cấu tử 2 tham số. Mục đích khởi tạo giá trị cho 2 trường tên và năm sinh cho Person.
      // Nơi sinh không được khởi tạo.
      public Person(String Name, int BornYear)
      {
          this.Name = Name;
          this.BornYear = BornYear;
      }

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

namespace InheritancePolymorphism
{
  class PersonDemo
  {

      static void Main(string[] args)
      {

          // Đối tượng: Thomas Edison.
          // Khởi tạo theo cấu tử 2 tham số.
          Person edison = new Person("Thomas Edison", 1847);

          Console.WriteLine("Info:");
          Console.WriteLine("Name: " + edison.Name);
          Console.WriteLine("Born Year: " + edison.BornYear);
          Console.WriteLine("Place Of Birth: " + edison.PlaceOfBirth);

          // Đối tượng: Bill Gates
          // Khởi tạo theo cấu tử 3 tham số.
          Person billGates = new Person("Bill Gate", 1955 ,"Seattle, Washington");

          Console.WriteLine("-----------------------------------");

          Console.WriteLine("Info:");
          Console.WriteLine("Name: " + billGates.Name);
          Console.WriteLine("Born Year: " + billGates.BornYear);
          Console.WriteLine("Place Of Birth: " + billGates.PlaceOfBirth);

          Console.ReadLine();
      }
  }
}
Kết quả chạy class PersonDemo:

Phân biệt Class, cấu tử và đối tượng:

Class Person mô phỏng một lớp người, nó là một thứ gì đó trìu tượng, nhưng nó có các trường để mang thông tin, trong ví dụ trên là tên, năm sinh, nơi sinh.

Cấu tử - Hoặc còn gọi là "Phương thức khởi tạo"
  • Cấu tử luôn có tên giống tên class
  • Một class có một hoặc nhiều cấu tử.
  • Cấu tử có hoặc không có tham số, cấu tử không có tham số còn gọi là cấu tử mặc định.
  • Cấu tử là cách để tạo ra một đối tượng của class.
Như vậy class Person (Mô tả lớp người) là thứ trìu tượng, nhưng khi chỉ rõ vào bạn hoặc tôi thì đó là 2 đối tượng thuộc class Person. Và cấu tử là phương thức đặc biệt để tạo ra đối tượng, cấu tử sẽ gán các giá trị vào các trường (field) của class cho đối tượng..
Đây là hình ảnh minh họa các trường của class được gán giá trị thế nào, khi bạn tạo đối tượng từ cấu tử.

3- Thừa kế trong CSharp

Chúng ta cần một vài class tham gia vào minh họa.
  • Animal: Class mô phỏng một lớp Động vật.
  • Duck: Class mô phỏng lớp vịt, là một class con của Animal.
  • Cat: Class mô phỏng lớp mèo, là một class con của Animal
  • Mouse: Class mô phỏng lớp chuột, là một class con của Animal.
Như đã biết ở phần trước, cấu tử của class (còn gọi là phương thức khởi tạo) sử dụng để tạo một đối tượng, và khởi tạo giá trị cho các trường (field).
Cấu tử của class con bao giờ cũng gọi tới một cấu tử ở class cha để khởi tạo giá trị cho các trường ở class cha, sau đó nó mới khởi tạo giá trị cho các trường của nó.
Hãy xem ví dụ:
Animal.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace InheritancePolymorphism
{
  public abstract class Animal
  {
      // Trường Name.
      // Tên, ví dụ Mèo Tom, Chuột Jerry.
      public string Name;

      // Cấu tử mặc định
      public Animal()
      {
          Console.WriteLine("- Animal()");
      }

      public Animal(string Name)
      {            
          // Gán giá trị cho trường Name.
          this.Name = Name;
          Console.WriteLine("- Animal(string)");
      }

      // Phương thức mô tả hành vi di chuyển của con vật.
      // virtual: Nói rằng phương thức này có thể ghi đè tại các class con.
      public virtual void Move()
      {
          Console.WriteLine("Animal Move");
      }

      public void Sleep()
      {
          Console.WriteLine("Sleep");
      }

  }
}
Cat là class con thừa kế từ class Animal, nó cũng có các trường của mình.
Cat.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace InheritancePolymorphism
{
   public class Cat : Animal
   {

       public int Age;
       public int Height;

       // Đây là cấu tử 3 tham số của Cat.
       // Sử dụng :base(name) để gọi đến cấu tử của class cha: Animal(string).
       // Các trường của class cha sẽ được gán giá trị.
       // Sau đó các trường của class này mới được gán giá trị.
       public Cat(string name, int Age, int Height)
           : base(name)
       {
           this.Age = Age;
           this.Height = Height;
           Console.WriteLine("- Cat(string,int,int)");
       }


       // Cấu tử này gọi tới cấu tử mặc định (Không tham số) của class cha.
       public Cat(int Age, int Height)
           : base()
       {
           this.Age = Age;
           this.Height = Height;
           Console.WriteLine("- Cat(int,int)");
       }

       public void Say()
       {
           Console.WriteLine("Meo");
       }


       // Viết lại hành vi di chuyển của loài Mèo.
       // Ghi đè phương thức Move() của class cha (Animal).
       public override void Move()
       {
           Console.WriteLine("Cat Move ...");
       }
   }
}
CatTest.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace InheritancePolymorphism
{
  class CatTest
  {
      static void Main(string[] args)
      {

          Console.WriteLine("Create Cat object from Cat(string,int,int)");

          // Khởi tạo một đối tượng Cat từ cấu tử 3 tham số.
          // Trường Name của Animal sẽ được gán giá trị "Tom".
          // Trường Age của Cat sẽ được gán giá trị 3
          // Trường Height của Cat sẽ được gán giá trị 20.
          Cat tom = new Cat("Tom",3, 20);

          Console.WriteLine("------");
         
          Console.WriteLine("Name = {0}", tom.Name);
          Console.WriteLine("Age = {0}", tom.Age);
          Console.WriteLine("Height = {0}", tom.Height);

          Console.WriteLine("------");

          // Gọi method thừa kế từ Animal
          tom.Move();

          // Gọi method Say() (của class Cat)
          tom.Say();


          Console.ReadLine();
      }
  }
}
Kết quả chạy class CatTest:
Điều gì đã xẩy ra khi bạn khởi tạo một đối tượng từ một cấu tử. Nó sẽ gọi lên một cấu tử của class cha như thế nào? Bạn hãy xem hình minh họa dưới đây:
Với hình minh họa trên bạn thấy rằng, cấu tử của class cha bao giờ cũng được gọi trước cấu tử của class con, nó sẽ gán giá trị cho các trường của class cha trước, sau đó các trường của class con mới được gán giá trị.
Khi bạn viết một cấu tử không khai báo rõ ràng nó base từ cấu tử nào của class cha, CSharp tự hiểu là cấu tử đó base từ cấu tử mặc định của class cha.
// Cấu tử  này không ghi rõ base từ cấu tử nào của class cha.
public Cat(int Age, int Height)
{

}

// Nó sẽ tương đương với:
public Cat(int Age, int Height) : base()
{
 
}
Một cấu tử có thể gọi tới một cấu tử khác sử dụng :this.
private int Weight;

// Cấu tử mặc định (Không có tham số).
// Gọi tới cấu tử Mouse(int)
public Mouse()    : this(100)
{
}

// Cấu tử 1 tham số.
// Không ghi rõ :base
// Nghĩa là base từ cấu tử mặc định của class cha
public Mouse(int Weight)
{            
   this.Weight = Weight;
}
Mouse.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace InheritancePolymorphism
{
  public class Mouse : Animal
  {

      private int Weight;

      // Cấu tử mặc định (Không có tham số).
      // Gọi tới cấu tử Mouse(int)
      public Mouse()
          : this(100)
      {
      }

      // Cấu tử 1 tham số.
      // Không ghi rõ :base
      // Nghĩa là base từ cấu tử mặc định của class cha
      public Mouse(int Weight)
      {            
          this.Weight = Weight;
      }

      // Cấu tử 2 tham số
      public Mouse(String name, int Weight)
          : base(name)
      {
          this.Weight = Weight;
      }

      public int GetWeight()
      {
          return Weight;
      }

      public void SetWeight(int Weight)
      {
          this.Weight = Weight;
      }


  }
}
Sử dụng toán tử 'is' bạn có thể kiểm tra một đối tượng có phải là kiểu của một class nào đó hay không. Hãy xem ví dụ dưới đây:
IsOperatorDemo.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace InheritancePolymorphism
{
  class IsOperatorDemo
  {
      static void Main(string[] args)
      {
          // Khởi tạo một đối tượng động vật.
          // Animal là class trìu tượng
          // nó ko thể tạo đối tượng từ cấu tử của nó.
          Animal tom = new Cat("Tom", 3, 20);

          Console.WriteLine("Animal Sleep:");
          // Gọi phương thức Sleep() của Animal
          tom.Sleep();

          // Sử dụng toán tử 'is' để kiểm tra xem
          // một đối tượng có phải kiểu nào đó không.
          bool isMouse = tom is Mouse;// false

          Console.WriteLine("Tom is mouse? " + isMouse);

          bool isCat = tom is Cat; // true
          Console.WriteLine("Tom is cat? " + isCat);

          bool isAnimal = tom is Animal; // true
          Console.WriteLine("Tom is animal? " + isAnimal);

          Console.ReadLine();
      }
  }
}
Chạy ví dụ:

Ép kiểu trong CSharp.

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

namespace InheritancePolymorphism
{
  class CastDemo
  {
      static void Main(string[] args)
      {
          // Gọi phương thức trả về một con vật ngẫu nhiên.
          Animal animal = GetRandomAnimal();

          if (animal is Cat)
          {
              Console.WriteLine("Your animal is Cat");

              // Ép kiểu về Cat.
              Cat cat = (Cat)animal;

              // Và truy cập vào trường Height của đối tượng Cat.
              Console.WriteLine("Cat height: " + cat.Height);
          }
          else if (animal is Mouse)
          {
              Console.WriteLine("Your animal is Mouse");

              // Ép kiểu về Mouse
              Mouse mouse = (Mouse)animal;

              // Và gọi method của class Mouse.
              Console.WriteLine("Mouse weight: " + mouse.GetWeight());
          }

          Console.ReadLine();
      }

      // Method trả về ngẫu nhiên một con vật.
      public static Animal GetRandomAnimal()
      {
          // Trả về giá trị ngẫu nhiên nằm giữa 0 và 9 (0,...9)
          // Creates a random number between 1 and 9
          int random = new Random().Next(0, 10);

          Console.WriteLine("random = " + random);

          Animal animal = null;
          if (random < 5)
          {
              Console.WriteLine("Create a Cat");
              animal = new Cat("Tom", 3, 20);
          }
          else
          {
              Console.WriteLine("Create a Mouse");
              animal = new Mouse("Jerry", 5);
          }
          return animal;
      }
  }
}
Chạy ví dụ:

4- Đa hình trong CSharp

Đa hình (Polymorphism) từ này có nghĩa là có nhiều hình thức. Trong mô hình lập trình hướng đối tượng, đa hình thường được diễn tả như là "một giao diện, nhiều chức năng".

Đa hình có thể là tĩnh hoặc động. Trong đa hình tĩnh, phản ứng với một chức năng được xác định tại thời gian biên dịch. Trong đa hình động, nó được quyết định tại thời gian chạy (run-time).
Bạn có một con mèo nguồn gốc châu Á (AsianCat), bạn có thể nói nó là một con mèo (Cat) hoặc nói nó là một con vật (Animal) đó là một khía cạnh của từ đa hình.

Hoặc một ví dụ khác: Trên lý lịch của bạn ghi rằng bạn là một người châu Á, trong khi đó bạn thực tế là một người Việt Nam. Và có thể rằng trong tương lai người ta sẽ cấp cho bạn một hộ chiếu khác ghi rằng bạn là người trái đất.

Ví dụ dưới đây cho bạn thấy cách hành sử giữa khai báo và thực tế:
PolymorphismCatDemo.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace InheritancePolymorphism
{
  class PolymorphismCatDemo
  {
      static void Main(string[] args)
      {

          // Bạn khai báo một đối tượng là một động vật (Animal).
          // Bằng cách tạo nó bởi cấu tử của class Cat.

          // Đối tượng 'tom' khai báo là Animal
          // vì vậy nó chỉ có thể gọi các phương thức của Animal.

          Animal tom = new Cat("Tom", 3, 20);


         
          // Gọi method Sleep Animal
          tom.Sleep();

          // Gọi method Move()
          // Move() là phương thức có trong Animal.
          // Move() được ghi đè trong class Cat.
          // 'tom' thực tế là Cat, nó sẽ gọi hàm viết đè trong Cat.
          tom.Move(); // ==> Cat Move.


          Console.ReadLine();
      }
  }
}
Bạn có 2 cách để ghi đè một phương thức từ class cha là overridenew. Hãy xem hình minh họa dưới đây:
Duck.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace InheritancePolymorphism
{
   class Duck : Animal
   {

       public Duck(string name)
           : base(name)
       {
           Console.WriteLine("- Duck(string)");
       }


       public new void Move()
       {
           Console.WriteLine("Duck Move..");
       }
   }
}
InheritanceDuckDemo.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace InheritancePolymorphism
{
  class InheritanceDuckDemo
  {
      static void Main(string[] args)
      {

          // Bạn khai báo một đối tượng là một động vật (Animal).
          // Bằng cách tạo nó bởi cấu tử của class Duck (Một con vịt).

          // Đối tượng 'donald' khai báo là Animal
          // vì vậy nó chỉ có thể gọi các phương thức của Animal.

          Animal donald = new Duck("Donald");



          // Gọi method Sleep của Animal
          donald.Sleep();

          // Gọi method Move()
          // Move() là phương thức có trong Animal.
          // Move() được ghi đè trong class Duck (Theo từ khóa new,
          // vì vậy chỉ các đối tượng khai báo là Duck hoặc con của Duck mới được dùng).

          // 'donald' thực tế là Duck, nhưng nó đang được khai báo là Animal.
          // (Phương thức Move() thừa kế từ Animal sẽ được gọi).
          donald.Move(); // ==> Animal Move.


          Console.ReadLine();
      }
  }
}
Chạy ví dụ: