Nội dung bài học

Tính kế thừa, tính đóng gói, tính đa hình, tính trừ tượng trong C#

Phần Kế thừa nằm đoạn cuối video này: phần kế thừa (phút 54:15)

TÍNH KẾ THỪA 

Lập trình hướng đối tượng có hai đặc trưng cơ bản: 

  • Đóng gói dữ liệu, được thể hiện bằng cách dùng khái niệm lớp để biểu diễn đối tượng với các thuộc tính private, chỉ cho phép bên ngoài truy nhập vào thông qua các phương thức get/set. 
  • Dùng lại mã, thể hiện bằng việc thừa kế giữa các lớp. Việc thừa kế cho phép các lớp thừa kế (gọi là lớp dẫn xuất) sử dụng lại các phương thức đã được định nghĩa trong các lớp gốc (gọi là lớp cơ sở). 

 

Khai báo thừa kế

Cú pháp khai báo một lớp kế thừa từ một lớp khác như sau: 

class <Tên lớp dẫn xuất>: <Tên lớp cơ sở>{ 
…  // Khai báo các thành phần lớp 
}; 

Ví dụ về đơn kế thừa C #: Kế thừa các trường

Khi một lớp kế thừa một lớp khác, nó được gọi là đơn thừa kế. Chúng ta hãy xem ví dụ về đơn thừa kế cấp chỉ kế thừa các trường chỉ đọc.

using System;  
   public class Employee  
    {  
       public float salary = 40000;  
   }  
   public class Programmer: Employee  
   {  
       public float bonus = 10000;  
   }  
   class TestInheritance{  
       public static void Main(string[] args)  
        {  
            Programmer p1 = new Programmer();  
  
            Console.WriteLine("Salary: " + p1.salary);  
            Console.WriteLine("Bonus: " + p1.bonus);  
  
        }  
    }  

Kết quả:

Salary: 40000
Bonus: 10000

 

Trong ví dụ trên,  Employee là lớp cơ sở còn Programmer là lớp dẫn xuất.

Ví dụ đơn kế thừa cấp: Phương thức kế thừa

Chúng ta hãy xem một ví dụ khác về kế thừa trong C # chỉ kế thừa các phương thức.

using System;  
   public class Animal  
    {  
       public void eat() { Console.WriteLine("Eating..."); }  
   }  
   public class Dog: Animal  
   {  
       public void bark() { Console.WriteLine("Barking..."); }  
   }  
   class TestInheritance2{  
       public static void Main(string[] args)  
        {  
            Dog d1 = new Dog();  
            d1.eat();  
            d1.bark();  
        }  
    }  

Kết quả:

Eating...
Barking...


Ví dụ về đa kế thừa

Khi một lớp kế thừa một lớp khác được kế thừa bởi một lớp khác, nó được gọi là đa kế thừa trong C#. Kế thừa là bắc cầu nên lớp dẫn xuất cuối cùng có được tất cả các thành viên của tất cả các lớp cơ sở của nó.

Hãy xem ví dụ về đa thừa kế trong C #.

using System;  
   public class Animal  
    {  
       public void eat() { Console.WriteLine("Eating..."); }  
   }  
   public class Dog: Animal  
   {  
       public void bark() { Console.WriteLine("Barking..."); }  
   }  
   public class BabyDog : Dog  
   {  
       public void weep() { Console.WriteLine("Weeping..."); }  
   }  
   class TestInheritance2{  
       public static void Main(string[] args)  
        {  
            BabyDog d1 = new BabyDog();  
            d1.eat();  
            d1.bark();  
            d1.weep();  
        }  
    }  

Kết quả:

Eating...
Barking...
Weeping...

 

Lưu ý: Trong C# không hỗ trợ tại một thời điểm không được thừa kế một lúc nhiều lớp.


C# Aggregation (Mối quan hệ HAS-A)

Trong C#, aggregation là một một mối quan hệ trong đó một lớp định nghĩa một lớp khác là bất kỳ tham chiếu đến một đối tượng nào đó. Đó là một cách khác để sử dụng lại lớp. Đó là một hình thức liên kết đại diện cho mối quan hệ HAS-A (có một).

Ví dụ:

Chúng ta hãy xem một ví dụ về tập hợp trong đó lớp Employee có tham chiếu của lớp Address là thành viên dữ liệu. Theo cách đó, nó có thể sử dụng lại các thành viên của lớp Address.

using System;  
public class Address  
{  
    public string addressLine, city, state;  
    public Address(string addressLine, string city, string state)  
    {  
        this.addressLine = addressLine;  
        this.city = city;  
        this.state = state;  
    }  
}  
   public class Employee  
    {  
       public int id;  
       public string name;  
       public Address address;//Employee HAS-A Address  
       public Employee(int id, string name, Address address)  
       {  
           this.id = id;  
           this.name = name;  
           this.address = address;  
       }  
       public void display()  
       {  
           Console.WriteLine(id + " " + name + " " +   
             address.addressLine + " " + address.city + " " + address.state);  
       }  
   }  
   public class TestAggregation  
   {  
        public static void Main(string[] args)  
        {  
            Address a1=new Address("G-13, Sec-3","Noida","UP");  
            Employee e1 = new Employee(1,"Sonoo",a1);  
            e1.display();  
        }  
    }  

Kết quả:

1 Sonoo G-13 Sec-3 Noida UP​

 


Tính đóng gói (encapsulation) trong C#

Tính đóng gói (encapsulation) "đóng gói" thuộc tính và phương thức của đối tượng (hoặc lớp) thông qua việc giới hạn quyền truy cập (hoặc thay đổi) giá trị của thuộc tính hoặc quyền gọi phương thức. Nói cách khác tính đóng gói cho phép kiểm soát quyền truy cập (và thay đổi) giá trị của thuộc tính hoặc quyền gọi phương thức của đối tượng (hoặc lớp) và đối tượng (hoặc lớp) con.

Một lớp được đóng gói đầy đủ có các hàm getter và setter được sử dụng để đọc và ghi dữ liệu. Lớp này không cho phép truy cập dữ liệu trực tiếp.

Ở đây, chúng ta đang tạo một ví dụ trong đó chúng ta có một lớp đóng gói các thuộc tính và cung cấp các hàm getter và setter.

Ví dụ:

namespace AccessSpecifiers  
{  
    class Student  
    {  
        // Creating setter and getter for each property  
        public string ID { get; set; }  
        public string Name { get; set; }  
        public string Email { get; set; }  
    }  
}  
using System;  
namespace AccessSpecifiers  
{  
    class Program  
    {  
        static void Main(string[] args)  
        {  
            Student student = new Student();  
            // Setting values to the properties  
            student.ID = "101";  
            student.Name = "Mohan Ram";  
            student.Email = "mohan@example.com";  
            // getting values  
            Console.WriteLine("ID = "+student.ID);  
            Console.WriteLine("Name = "+student.Name);  
            Console.WriteLine("Email = "+student.Email);  
        }  
    }  
}  

Kết quả:

ID = 101
Name = Mohan Ram
Email = mohan@example.com​

 



TÍNH ĐA HÌNH

Nạp chồng (Overloading) trong C#

Trong một lớp, ta có thể tạo ra nhiều hàm với cùng một tên gọi nhưng khác nhau các dữ liệu đầu vào hoặc tham số, đó gọi là nạp chồng. Trong C++, Chúng ta có thể nạp chồng:

  • Phương thức (methods),
  • Phương thức thiết lập(constructors), 
  • indexed properties

Các kiểu quá nạp chồng trong C#:

  • Nạp chồng phương thức
  • Nạp chồng các toán tử 


Nạp chồng phương thức

Trong một lớp, ta có thể tạo ra nhiều hàm với cùng một tên gọi nhưng khác nhau các dữ liệu đầu vào hoặc tham số, đó gọi là nạp chồng phương thức.
Lới ích của việc quá tải phương thức là chúng ta có thể khai báo cùng một tên phương thức trong cùng chương trình, không cần phải khai báo tên khác cho cùng một hành đông.

Ví dụ nạp chồng phương thức C#

Ví dụ  trong lớp Cal sau có hai phương thức add nhưng khác nhau về tham số:

using System;  
public class Cal{  
    public static int add(int a,int b){  
        return a + b;  
    }  
    public static int add(int a, int b, int c)  
    {  
        return a + b + c;  
    }  
}  
public class TestMemberOverloading  
{  
    public static void Main()  
    {  
        Console.WriteLine(Cal.add(12, 23));  
        Console.WriteLine(Cal.add(12, 23, 25));  
    }  
}  

Kết quả:

35
60

 


Ví dụ về nạp chồng: Bằng cách thay đổi kiểu dữ liệu của các tham số

Chúng ta hãy xem một ví dụ khác về nạp chồng phương thức trong đó chúng ta sẽ  thay đổi kiểu dữ liệu của đối số.

using System;  
public class Cal{  
    public static int add(int a, int b){  
        return a + b;  
    }  
    public static float add(float a, float b)  
    {  
        return a + b;  
    }  
}  
public class TestMemberOverloading  
{  
    public static void Main()  
    {  
        Console.WriteLine(Cal.add(12, 23));  
        Console.WriteLine(Cal.add(12.4f,21.3f));  
    }  
}  

Kết quả:

35
33.7

 



Phương thức Ghi đè (Overriding) trong C#

Trong C#, nếu lớp dẫn xuất định nghĩa cùng một phương thức đã  được định nghĩa trong lớp cơ sở, nó được gọi là phương thức ghi đè. Phương thức ghi đè được sử dụng để đạt được tính đa hìn . Phương thức ghi đè cho phép bạn cung cấp việc thực hiện cụ thể chức năng đã được lớp cơ sở của nó cung cấp.

Phương thức ghi đè thường được sử dụng trong phương thức ở lớp con.
Một số quy tắc sử dụng phương thức ghi đè:

  • Các phương thức static thì không ghi đè nhưng được mô tả lại.
  • Các phương thức không kế thừa sẽ không được overriden (hiển nhiên).

Để thực hiện ghi đè phương thức trong C #, bạn cần sử dụng từ virtual với phương thức lớp cơ sở và từ khóa override cho phương thức lớp dẫn xuất.

Ví dụ sau chúng ta ghi đè phương thức eat()

Chúng ta hãy xem một ví dụ đơn giản về phương thức ghi đè trong C#. Trong ví dụ này, chúng ta đang ghi đè phương thức eat () bằng từ khóa override.

using System;  
public class Animal{  
    public virtual void eat(){  
        Console.WriteLine("Eating...");  
    }  
}  
public class Dog: Animal  
{  
    public override void eat()  
    {  
        Console.WriteLine("Eating bread...");  
    }  
}  
public class TestOverriding  
{  
    public static void Main()  
    {  
        Dog d = new Dog();  
        d.eat();  
    }  
}  

Kết quả:

Eating bread...​

 



Từ khóa Base trong C#

Trong C #, từ khóa base được sử dụng để truy cập các trường, hàm khởi tạo và phương thức của lớp cơ sở.
Bạn chỉ có thể sử dụng từ khóa base trong phương thức, hàm khởi tạo hoặc hàm truy cập thuộc tính của đối tượng. Bạn không thể sử dụng nó trong phương thức tĩnh.

Ví dụ về từ khóa base: truy cập trường của lớp cơ sở

Chúng ta có thể sử dụng từ khóa base để truy cập vào các trường của lớp cơ sở trong lớp dẫn xuất. Nó rất hữu ích nếu các lớp cơ sở và các lớp dẫn xuất có cùng các trường. Nếu lớp dẫn xuất không định nghĩa cùng một trường, thì không cần sử dụng từ khóa cơ sở. Trường trong lớp cơ sở có thể được truy cập trực tiếp bởi lớp dẫn xuất.

using System;  
public class Animal{  
    public string color = "white";  
}  
public class Dog: Animal  
{  
    string color = "black";  
    public void showColor()  
    {  
        Console.WriteLine(base.color);  
        Console.WriteLine(color);  
    }  
      
}  
public class TestBase  
{  
    public static void Main()  
    {  
        Dog d = new Dog();  
        d.showColor();  
    }  
}  

Kết quả:

white
black



Tính Đa hình (Polymorphism) trong C#

Đa hình là thuật ngữ được dùng trong hướng đối tượng dùng để chỉ việc ứng xử khác nhau của các đối tượng trong những lớp kế thừa từ một lớp cơ sở khi một phương thức chung được gọi. Tính đa hình được sử dụng trong trường hợp một phương thức chung được sử dụng trong nhiều lớp dẫn xuất nhưng ứng với mỗi lớp dẫn xuất cụ thể thì phương thức này có những ứng xử khác nhau. Để thực hiện được tính đa hình, phương thức ở lớp cơ sở phải được mô tả ở dạng ảo (virtual) và phương thức đó ở lớp dẫn xuất phải được ghi đè (override). Override là thuật ngữ được dùng để chỉ việc lớp dẫn xuất đặc tả lại phương thức ở lớp cơ sở. Ví dụ sau trình bày việc thực hiện tính năng đa hình khi xây dựng một lớp cơ sở và hai lớp dẫn xuất.

Có hai loại đa hình trong C #: đa hình thời gian biên dịch và đa hình thời gian chạy.
Đa hình thời gian biên dịch bằng cách nạp chồng phương thức và nạp chồng toán tử trong C #. Nó còn được gọi là ràng buộc tĩnh hoặc ràng buộc sớm.
Đa hình thời gian chạy bằng phương pháp ghi đè, còn được gọi là liên kết động hoặc liên kết muộn.

Ví dụ 1: đa hình thời gian chạy C#

Chúng ta hãy xem một ví dụ đơn giản về đa hình thời gian chạy trong C#.

using System;  
public class Animal{  
    public virtual void eat(){  
        Console.WriteLine("eating...");  
    }  
}  
public class Dog: Animal  
{  
    public override void eat()  
    {  
        Console.WriteLine("eating bread...");  
    }  
      
}  
public class TestPolymorphism  
{  
    public static void Main()  
    {  
        Animal a= new Dog();  
        a.eat();  
    }  
}  

Kết quả:

eating bread...

 

Ví dụ 2: Đa hình thời gian chạy C#

Chúng ta hãy xem một ví dụ khác về đa hình thời gian chạy trong C# , ở đây có hai lớp dẫn xuất.

using System;  
public class Shape{  
    public virtual void draw(){  
        Console.WriteLine("drawing...");  
    }  
}  
public class Rectangle: Shape  
{  
    public override void draw()  
    {  
        Console.WriteLine("drawing rectangle...");  
    }  
      
}  
public class Circle : Shape  
{  
    public override void draw()  
    {  
        Console.WriteLine("drawing circle...");  
    }  
  
}  
public class TestPolymorphism  
{  
    public static void Main()  
    {  
        Shape s;  
        s = new Shape();  
        s.draw();  
        s = new Rectangle();  
        s.draw();  
        s = new Circle();  
        s.draw();  
  
    }  
}  

Kết quả:

drawing...
drawing rectangle...
drawing circle...

 

 

Đa hình trong lúc chạy với các thành viên dữ liệu

Đa hình thời gian chạy không thể đạt được bởi các thành viên dữ liệu trong C#. Chúng ta hãy xem một ví dụ: Chúng ta đang truy cập vào trường bằng biến tham chiếu đến đối tượng của lớp dẫn xuất.

using System;  
public class Animal{  
    public string color = "white";  
  
}  
public class Dog: Animal  
{  
    public string color = "black";  
}  
public class TestSealed  
{  
    public static void Main()  
    {  
        Animal d = new Dog();  
        Console.WriteLine(d.color);  
    
    }  
}  

Kết quả:

white​

 


Lớp niêm phong (Sealed class) 

  • Từ khóa sealed được sử dụng để biểu thị khi khai báo một class nhằm ngăn ngừa sự dẫn xuất từ một class, điều này cũng giống như việc ngăn cấm một class nào đó có class con.
  • Một class sealed cũng không thể là một class trừu tượng.
  • Các structs trong C# được ngầm định sealed. Do vậy, chúng không thể được thừa kế


Cú pháp:

 sealed class tên_class
{
// Các thành viên của class trừu tượng.
}

Lưu ý: Các kiểu cấu trúc được niêm phong ngầm do đó chúng không thể được thừa kế.

Lớp niêm phong C# không thể được dẫn xuất bởi bất kỳ lớp nào.

Chúng ta hãy xem một ví dụ về lớp niêm phong trong C#.

using System;  
sealed public class Animal{  
    public void eat() { Console.WriteLine("eating..."); }  
}  
public class Dog: Animal  
{  
    public void bark() { Console.WriteLine("barking..."); }  
}  
public class TestSealed  
{  
    public static void Main()  
    {  
        Dog d = new Dog();  
        d.eat();  
        d.bark();  
  
  
    }  
}  

Kết quả:

Compile Time Error: 'Dog': cannot derive from sealed type 'Animal'

 

 

Phương pháp niêm phong C#

Phương pháp niêm phong trong C# không thể được ghi đè. Nó phải được sử dụng với từ khóa ghi đè trong phương thức.

Chúng ta hãy xem một ví dụ về phương pháp niêm phong trong C#.

using System;  
public class Animal{  
    public virtual void eat() { Console.WriteLine("eating..."); }  
    public virtual void run() { Console.WriteLine("running..."); }  
  
}  
public class Dog: Animal  
{  
    public override void eat() { Console.WriteLine("eating bread..."); }  
    public sealed override void run() {   // Phương thức niêm phong
    Console.WriteLine("running very fast...");   
    }  
}  
public class BabyDog : Dog  
{  
    public override void eat() { Console.WriteLine("eating biscuits..."); }  
    public override void run() { Console.WriteLine("running slowly..."); }  
}  
public class TestSealed  
{  
    public static void Main()  
    {  
        BabyDog d = new BabyDog();  
        d.eat();  
        d.run();  
    }  
}  

Kết quả:

Compile Time Error: 'BabyDog.run()': cannot override inherited member 'Dog.run()' because it is sealed

 

Lưu ý: Các biến cục bộ không thể được niêm phong.
using System;  
public class TestSealed  
{  
    public static void Main()  
    {  
        sealed int x = 10;  
        x++;  
        Console.WriteLine(x);  
    }  
}  

Kết quả:

Compile Time Error: Invalid expression term 'sealed'

 



Tính trừu tượng (Abstract Class) trong C#

Lớp trừu tượng đơn giản được xem như một class cha cho tất cả các Class có cùng bản chất. Do đó mỗi lớp dẫn xuất (lớp con) chỉ có thể kế thừa từ một lớp trừu tượng. Bên cạnh đó nó không cho phép tạo instance, nghĩa là sẽ không thể tạo được các đối tượng thuộc lớp đó.

Lớp Abstract sẽ định nghĩa tất cả các phương thức để các lớp dẫn xuất khi kế thừa từ nó phải định nghĩa lại (Override) – Tính đa hình trong lập trình hướng đối tượng OOP. Do đó kể lớp dẫn xuất có thể kế thừa và định nghĩa lại thì các tất cả các phương thức trong lớp Abstract bắt buộc phải sử dụng từ khoá truy cập là public hoặc protected. Không thể sử dụng từ khoá private. Lớp Abstract có thể có thuộc tính nhưng ko được sử dụng từ khoá abstract cho thuộc tính. Mặt khác, Abstract class không cho phép bạn khởi tạo một object từ nó được.

Trừu tượng có thể làm bằng hai cách:

    1. Lớp trừu tượng (Abstract class)
    2. Giao diện (Interface)

Cả lớp trừu tượng và giao diện đều có thể có các phương thức trừu tượng cần thiết cho sự trừu tượng hóa.

Phương pháp trừu tượng

Một phương thức được khai báo là trừu tượng và không có phần thân được gọi là phương thức trừu tượng. Nó chỉ có thể được khai báo bên trong lớp trừu tượng. Việc thực hiện của nó phải được cung cấp bởi các lớp dẫn xuất.

Ví dụ:

public abstract void draw();  
Lưu ý: Một phương thức trừu tượng trong C # bên trong là một phương thức ảo để nó có thể bị ghi đè bởi lớp dẫn xuất.


Lớp trừu tượng C#

Trong C #, lớp trừu tượng là một lớp được khai báo là trừu tượng. Nó có thể có các phương pháp trừu tượng hoặc  không trừu tượng. Nó không thể được khởi tạo. Việc thực hiện của nó phải được cung cấp bởi các lớp dẫn xuất. Ở đây, lớp dẫn xuất buộc phải thực hiện tất cả các phương thức trừu tượng.

Chúng ta hãy xem một ví dụ về lớp trừu tượng trong C# có một phương thức trừu tượng draw (). Việc thực hiện của nó  bởi các lớp dẫn xuất: Hình chữ nhật và Vòng tròn. Cả hai lớp có cách thực hiện khác nhau.

using System;  
public abstract class Shape  
{  
    public abstract void draw();  
}  
public class Rectangle : Shape  
{  
    public override void draw()  
    {  
        Console.WriteLine("drawing rectangle...");  
    }  
}  
public class Circle : Shape  
{  
    public override void draw()  
    {  
        Console.WriteLine("drawing circle...");  
    }  
}  
public class TestAbstract  
{  
    public static void Main()  
    {  
        Shape s;  
        s = new Rectangle();  
        s.draw();  
        s = new Circle();  
        s.draw();  
    }  
}  

Kết quả:

drawing ractangle...
drawing circle...

 



Giao diện (interface) trong C#

Khi nhìn qua ta thấy Interface rất giống với Abstract trong C# ? Nhưng về bản chất bên trong hoàn toàn khác nhau. Interface không phải là một lớp cụ thể mà là một khuôn mẫu để cho một đối tượng implement nó, và đương nhiên là ta không thể tạo một biến Interface. Ngược lại lớp Abstract là một lớp cụ thể, có đầy đủ các tính chất của một đối tượng, có thể gọi, định nghĩa các hàm trong nó. Đối với hằng số ở lớp implement không được định nghĩa lại.

Cú pháp:

 interface [interfaceName]

Một số điểm cần lưu ý khi làm việc với interface.

  • Lớp kế thừa không được định nghĩa lại hằng số đã được định nghĩa trong interface.
  • Định nghĩ đúng các hàm của lớp kế thừa như trong interface.
  • Định nghĩa đúng mức truy cập các hàm của lớp kế thừa như trong interface.
  • Một interface chứa các hành vi(Action) mà một class(lớp) triển khai.
  • Một interface có thể bao gồm bất cứ lượng phương thức nào.
  • Bạn không thể khởi tạo một interface.
  • Một interface không chứa bất cứ hàm contructor nào.
  • Tất cả các phương thức của interface đều là abstract.
  • Một interface không thể chứa một trường nào trừ các trường vừa static và final.
  • Một interface không thể kế thừa từ lớp, nó được triển khai bởi một lớp.
  • Một interface có thể kế thừa từ nhiều interface khác.


Ví dụ giao diện C#

Hãy xem ví dụ về giao diện trong C# có phương thức draw (). Việc thực hiện của nó được cung cấp bởi hai lớp: Rectangle và Circle.

using System;  
public interface Drawable  
{  
    void draw();  
}  
public class Rectangle : Drawable  
{  
    public void draw()  
    {  
        Console.WriteLine("drawing rectangle...");  
    }  
}  
public class Circle : Drawable  
{  
    public void draw()  
    {  
        Console.WriteLine("drawing circle...");  
    }  
}  
public class TestInterface  
{  
    public static void Main()  
    {  
        Drawable d;  
        d = new Rectangle();  
        d.draw();  
        d = new Circle();  
        d.draw();  
    }  
}  

Kết quả:

drawing ractangle...
drawing circle...

 

Lưu ý: Mặc định các phương thức giao diện là public và trừu tượng. Chúng ta không thể sử dụng từ khóa public và abstract cho phương thức giao diện

using System;  
public interface Drawable  
{  
    public abstract void draw();//Compile Time Error  
}  

 


Cộng đồng Automation Testing Việt Nam:


🌱 Zalo
Automation Testing:   https://zalo.me/g/lsxswc560
🌱 Facebook Group: Cộng đồng Automation Testing Việt Nam (Website, Desktop, Mobile)
🌱 Facebook Fanpage: Cộng đồng Automation Testing Việt Nam - Selenium

About the author

  • Anh Tester

    Đường dẫu khó chân vẫn cần bước đi
    Đời dẫu khổ tâm vẫn cần nghĩ thấu



Cộng đồng Automation Testing Việt Nam:


🌱 Zalo
Automation Testing:   https://zalo.me/g/lsxswc560
🌱 Facebook Group: Cộng đồng Automation Testing Việt Nam (Website, Desktop, Mobile)
🌱 Facebook Fanpage: Cộng đồng Automation Testing Việt Nam - Selenium

Chia sẻ kiến thức lên trang

Bạn có thể đăng bài để chia sẻ kiến thức, bài viết của chính bạn lên trang Anh Tester Blog

Danh sách bài học