CodeGym /Các khóa học /C# SELF /Phương thức và thuộc tính abstract

Phương thức và thuộc tính abstract

C# SELF
Mức độ, Bài học
Có sẵn

1. Phương thức abstract

Ở bài giảng trước, tụi mình đã đặt nền móng: làm quen với abstract class và thấy rằng chúng có thể chứa phương thức và thuộc tính abstract. Tụi mình hiểu rằng đây là "hợp đồng" bắt buộc các class con phải tự viết code thực hiện.

Giờ thì cùng đào sâu hơn chút và xem các tình huống thực tế phức tạp hơn. Trong bài này, tụi mình sẽ tập trung vào cú pháp, cách abstraction hoạt động trong hệ thống phân cấp nhiều tầng, và phân biệt rõ khi nào nên dùng abstract, khi nào nên dùng virtual.

Phương thức abstract giống như một công thức bí ẩn trong sách nấu ăn cũ: "thêm nguyên liệu bí mật" — là gì thì không nói, nhưng tất cả đầu bếp sau này đều phải tự nghĩ ra!

Phương thức abstract luôn nằm trong abstract class

Nếu bạn thử khai báo phương thức abstract trong class thường (không phải abstract), sẽ bị lỗi compile ngay. Tại sao? Vì class thường có thể tạo object trực tiếp, mà nếu gọi một phương thức chưa có code thì sao? Chỉ nhận được ánh mắt khó hiểu từ IDE thôi.


// Cái này sẽ bị lỗi compile:
public class WrongClass
{
    public abstract void Oops(); // Không được làm vậy!
}

Nếu class có ít nhất một phương thức abstract, nó bắt buộc phải được đánh dấu abstract!

Triển khai phương thức abstract trong class con

Triển khai bằng từ khóa override. Class kế thừa abstract class mà không triển khai hết các phương thức abstract thì cũng phải là abstract (không thì compiler sẽ la làng).

Tiếp tục ý tưởng app của tụi mình:

Ở các bài trước, tụi mình tạo class Shape (hình), trong đó khai báo phương thức abstract để tính diện tích. Giờ tạo các hình cụ thể nhé:


public class Rectangle : Shape
{
    public double Width { get; }
    public double Height { get; }

    public Rectangle(double width, double height)
    {
        Width = width;
        Height = height;
    }

    public override double CalculateArea()
    {
        return Width * Height;
    }
}

public class Circle : Shape
{
    public double Radius { get; }

    public Circle(double radius)
    {
        Radius = radius;
    }

    public override double CalculateArea()
    {
        return Math.PI * Radius * Radius;
    }
}

Tại sao phương thức abstract lại quan trọng?

Phương thức abstract là cách bạn nói: "Mấy bạn, nếu kế thừa class này thì tự quyết định cách làm việc này nhé!" Nó làm kiến trúc chương trình rõ ràng hơn, tránh bỏ sót, và quan trọng nhất là cho phép dùng polymorphism hết cỡ.

2. Thuộc tính abstract (properties)

Thuộc tính abstract là gì

Thuộc tính abstract cũng là một hợp đồng như phương thức abstract, nhưng dành cho property (property). Với C#, điều này rất tự nhiên vì property là cách chính để đóng gói dữ liệu. Thuộc tính abstract nói rằng: "Để con cháu tự quyết định lấy (và có thể set) giá trị này ở đâu".

Ví dụ:
Thêm thuộc tính Name cho hình — mỗi class con đều phải có, nhưng mỗi class tự quyết định trả về gì.


public abstract class Shape
{
    public abstract string Name { get; }

    public abstract double CalculateArea();
}

Giờ mỗi class con bắt buộc phải triển khai property này:


public class Rectangle : Shape
{
    public double Width { get; }
    public double Height { get; }
    public override string Name => "Hình chữ nhật";

    public Rectangle(double width, double height)
    {
        Width = width;
        Height = height;
    }

    public override double CalculateArea()
    {
        return Width * Height;
    }
}

public class Circle : Shape
{
    public double Radius { get; }
    public override string Name => "Hình tròn";

    public Circle(double radius)
    {
        Radius = radius;
    }

    public override double CalculateArea()
    {
        return Math.PI * Radius * Radius;
    }
}

Đặc điểm cú pháp

Thuộc tính abstract giống như trong interface: khai báo không có thân, nhưng phải chỉ rõ có getter hay cả setter.


public abstract class Creature
{
    // thuộc tính chỉ đọc
    public abstract string Species { get; }

    // thuộc tính đọc-ghi
    public abstract int Age { get; set; }
}

Trong class triển khai, bạn có thể tự viết logic lấy (và nếu cần thì set) giá trị.

Tại sao cần thuộc tính abstract

Thuộc tính abstract cực kỳ hữu ích khi bạn muốn mỗi class con tự quyết định cách lấy hoặc lưu giá trị. Thường dùng trong mô hình hóa, làm việc với business model, view model, hoặc bất cứ đâu logic property phức tạp hơn chỉ là trả về một field.

Ví dụ, nếu một hình có tên tính bằng công thức, hình khác trả về hằng số, tất cả đều có thể "giấu" đẹp đẽ sau property abstract.

3. Sơ đồ: abstract class, phương thức và thuộc tính liên quan thế nào

Nhìn sơ đồ sau để thấy cách nó hoạt động nhé:


┌─────────────┐
│ abstract    │
│   Shape     │
│-------------│
│ +Name: str  │  <-- thuộc tính abstract
│ +Area(): dbl│  <-- phương thức abstract
└─────┬───────┘
      │
 ┌────▼────┐      ┌───────┐
 │Rectangle│      │ Circle│
 │........ │ .... │ ......│
 │+Name    │      │+Name  │
 │+Area()  │      │+Area()│
 └─────────┘      └───────┘

Như vậy, tụi mình đảm bảo MỘT interface làm việc với bất kỳ class con nào của Shape, nhưng mỗi class con có thể làm "dưới nắp capo" kiểu gì cũng được.

UML cho phương thức và thuộc tính abstract:


┌────────────────────────────┐
│        abstract class      │
│           Animal           │
│────────────────────────────│
│+ Name: string {abstract}   │
│+ MakeSound(): void {abstract}│
└─────────────┬──────────────┘
              │
     ┌────────┴──────────┐
     │                   │
┌────────────┐     ┌─────────────┐
│    Cat     │     │    Dog      │
│────────────│     │─────────────│
│+ Name      │     │+ Name       │
│+ MakeSound()│    │+ MakeSound()│
└────────────┘     └─────────────┘

4. Tình huống thực tế và lợi ích cho dự án thật

Phương thức và thuộc tính abstract là công cụ của bạn khi thiết kế hệ thống class cần phát triển lâu dài. Đây là nền tảng cho các ứng dụng business lớn, mô hình nhiều tầng, hệ thống plugin, UI framework, nơi bạn đặt ra "luật" bắt buộc cho tất cả class con.

Trong dự án thật, những giải pháp kiến trúc "sạch" kiểu này giúp bạn sau nhiều tháng, nhiều năm vẫn tự tin thêm entity, chức năng mới, vì code cũ đã tính đến mọi trường hợp.

Câu hỏi "để làm gì" và quay lại với polymorphism

Polymorphism nghe như phép thuật, nhưng thực ra chỉ là hợp đồng: "bạn có thể gọi một phương thức nào đó trên bất kỳ object nào trong họ, và luôn nhận được kết quả phù hợp". Phương thức và thuộc tính abstract là cách bắt cả hệ thống class nói chung một ngôn ngữ, nhưng không ép buộc phải có code mặc định.

Cách này dùng rất nhiều trong hệ thống plugin (IDE, phần mềm đồ họa, CRM...), khi dev bên ngoài viết extension nhưng phải triển khai tối thiểu các hàm mà platform yêu cầu. Ví dụ, nếu bạn viết module xử lý file, class gốc có thể chứa thuộc tính abstract FileExtension và phương thức abstract Open(), để bất kỳ plugin nào cũng xử lý đúng loại file của mình.

Khác biệt giữa abstract, virtual và thành viên thường của class

Đặc điểm Phương thức/thuộc tính thường virtual abstract
Có code thực hiện không Có Có Không
Bắt buộc override không Không Không (có thể override) Có (bắt buộc ở class con)
Có thể gọi trực tiếp không Có Có Không
Có thể nằm trong class thường không Có Có Không
Có thể đánh dấu sealed không Không Có Không

5. Ứng dụng vào app học tập của tụi mình

Giờ, với kiến thức mới, cùng quay lại app hình học và xem thuộc tính, phương thức abstract phối hợp thế nào để tạo hệ thống linh hoạt, dễ hiểu nhé.


// Ví dụ, trong hàm chính của chương trình
List<Shape> shapes = new List<Shape>
{
    new Rectangle(4, 5),
    new Circle(2.5)
};

foreach (Shape shape in shapes)
{
    // Nhờ thuộc tính abstract Name và phương thức CalculateArea,
    // tụi mình không cần quan tâm loại hình cụ thể
    Console.WriteLine($"{shape.Name}, Diện tích: {shape.CalculateArea():F2}");
}

Kết quả:

Hình chữ nhật, Diện tích: 20.00
Hình tròn, Diện tích: 19.63

Quá đẹp: code không biết và không cần biết hình nào — chỉ cần gọi property, method, còn .NET CLR lo phần còn lại!

6. Lỗi thường gặp và lưu ý khi triển khai

Lỗi số 1: chưa triển khai phương thức abstract của cha.
Nếu class con còn sót lại phương thức hoặc thuộc tính abstract chưa triển khai, mà class đó không đánh dấu abstract, compiler sẽ không cho build. Đây là bảo vệ hữu ích: bạn không thể tạo object với logic "lủng" — ví dụ, hình mà không có hàm CalculateArea().

Lỗi số 2: khai báo phương thức là abstract, nhưng class lại không phải abstract.
Code kiểu này cũng không compile được. Nếu bạn thêm phương thức abstract vào class, thì class đó cũng phải là abstract:


public abstract class Polygon : Shape
{
    // Không triển khai CalculateArea(), class vẫn là abstract
}

Lỗi số 3: cố gắng triển khai phương thức abstract bằng virtual.
Phương thức abstract phải được triển khai bằng từ khóa override, không phải virtual. Tuy nhiên, nếu bạn muốn cho phép override tiếp ở class con sâu hơn, bạn có thể override rồi lại khai báo virtual ở class con:


public override double CalculateArea()
{
    // Code mặc định...
}

Ở class con sâu hơn, bạn có thể override tiếp. Nhưng không thể khai báo lại là abstract, vì đã có code rồi.

2
Nhiệm vụ
C# SELF, mức độ, bài học
Đã khóa
Khai báo lớp trừu tượng và phương thức
Khai báo lớp trừu tượng và phương thức
2
Nhiệm vụ
C# SELF, mức độ, bài học
Đã khóa
Các phương thức và thuộc tính trừu tượng
Các phương thức và thuộc tính trừu tượng
Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION