CodeGym /Các khóa học /C# SELF /Trừu tượng hóa trong xây dựng hệ thống phân cấp

Trừu tượng hóa trong xây dựng hệ thống phân cấp

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

1. Giới thiệu

Thử nhìn abstraction từ trên cao: tưởng tượng một văn phòng lớn, nơi nhiều nhân viên làm các việc khác nhau. Họ có thể có chung chức danh — "Nhân viên", mỗi người lại có nhiệm vụ riêng. Nhưng nếu bạn là sếp và muốn ai cũng có thể "LamViec", bạn chẳng cần quan tâm họ làm thế nào: lập trình viên thì code, kế toán thì gõ báo cáo. Ý tưởng "contract chung cho tất cả qua abstraction, chi tiết để chuyên gia lo" chính là nền tảng để xây dựng hệ thống class hợp lý.

Code sẽ như nào?

Mọi thứ bắt đầu từ abstract class cơ bản. Nó mô tả cái quan trọng nhất mà tất cả các class con phải có.


public abstract class NhanVien
{
    public string Ten { get; }
    public NhanVien(string ten)
    {
        Ten = ten;
    }

    // Phương thức abstract — contract cho mọi nhân viên
    public abstract void LamViec();
}

Tiếp theo là các class con — laptrinhvien và ketoan:


public class LapTrinhVien : NhanVien
{
    public LapTrinhVien(string ten) : base(ten) { }
    public override void LamViec()
    {
        Console.WriteLine($"{Ten} viet code");
    }
}

public class KeToan : NhanVien
{
    public KeToan(string ten) : base(ten) { }
    public override void LamViec()
    {
        Console.WriteLine($"{Ten} tinh tien");
    }
}

Giờ chú ý nhé: mình có thể tạo list nhân viên — ketoan, laptrinhvien, hay ai cũng được (abstraction không cấm đâu!).


NhanVien[] vanphong = new NhanVien[]
{
    new LapTrinhVien("Vasya"),
    new KeToan("Tanya")
};

foreach (NhanVien nv in vanphong)
{
    nv.LamViec(); // Ai làm việc nấy
}

Vasya viet code
Tanya tinh tien

Thấy không, mọi thứ giờ đẹp, linh hoạt và dễ mở rộng. Thêm class mới, ví dụ QuanLy, thì code cũ vẫn chạy ngon lành!

2. Xây dựng hệ thống phân cấp dựa trên abstraction

Gần như sách OOP nào cũng có ví dụ về động vật, nên mình cũng theo truyền thống luôn.

Abstract class — vai trò "Ông Trùm"


public abstract class DongVat
{
    public string Ten { get; set; }
    public DongVat(string ten)
    {
        Ten = ten;
    }

    // Mọi động vật đều phải biết kêu
    public abstract void Keu();

    // Nhưng không phải ai cũng bay được, nên:
    public virtual void Bay()
    {
        Console.WriteLine("Toi khong biet bay.");
    }
}

Class con: phải biết kêu, có thể override method khác


public class Meo : DongVat
{
    public Meo(string ten) : base(ten) { }
    public override void Keu()
    {
        Console.WriteLine($"{Ten}: Meo!");
    }
}

public class Cho : DongVat
{
    public Cho(string ten) : base(ten) { }
    public override void Keu()
    {
        Console.WriteLine($"{Ten}: Gau!");
    }
}

public class DaiBang : DongVat
{
    public DaiBang(string ten) : base(ten) { }
    public override void Keu()
    {
        Console.WriteLine($"{Ten}: Khet!");
    }

    public override void Bay()
    {
        Console.WriteLine($"{Ten} bay tren troi!");
    }
}

Giờ thử gom nhiều động vật vào collection:


DongVat[] soThu = new DongVat[]
{
    new Meo("Barsik"),
    new Cho("Sharik"),
    new DaiBang("Orel")
};

foreach (DongVat dv in soThu)
{
    dv.Keu();
    dv.Bay();
}

Barsik: Meo!
Toi khong biet bay.
Sharik: Gau!
Toi khong biet bay.
Orel: Khet!
Orel bay tren troi!

Thấy không, giờ dùng class con nào cũng dễ: mình không cần biết object nào trong collection. Abstraction lo contract, còn chi tiết thì class con xử lý.

3. Abstraction nhiều tầng

Abstraction không chỉ là chia base class và class con trực tiếp. Trong hệ thống phức tạp, nó thường có nhiều tầng, một abstract class xây trên cái khác. Kiểu như bánh nhiều lớp (hay củ hành như Shrek nói): mỗi lớp che bớt chi tiết, chỉ để lại cái cần thiết.

Ví dụ: Hệ thống phân cấp phương tiện giao thông


.              PhuongTien (abstract)
            /           |           \
       Oto           MayBay      Thuyen
  OtoDien    MayBayPhanLuc    ThuyenBuom

Ở đây PhuongTien đặt ra nguyên tắc chung (ví dụ method DiChuyen()), nhưng không biết oto hay maybay di chuyển thế nào. Class con sẽ quyết định. Class cụ thể như OtoDien hay MayBayPhanLuc có thể thêm chi tiết riêng, mở rộng chức năng.

Sơ đồ hệ thống phân cấp:


.       +------------------+
        |  PhuongTien      |  <--- abstract class
        +------------------+
        /        \
   +-------+   +-------+
   |  Oto  |   | Thuyen|  <--- abstract trung gian hoặc class cụ thể
   +-------+   +-------+

Code: abstract class cơ bản


public abstract class PhuongTien
{
    public string Mau { get; }
    public PhuongTien(string mau)
    {
        Mau = mau;
    }

    // Phương thức abstract
    public abstract void DiChuyen();
}

Abstract class trung gian

Đôi khi tầng giữa cũng cần abstraction riêng!


public abstract class Oto : PhuongTien
{
    public Oto(string mau) : base(mau) { }

    public override void DiChuyen()
    {
        Console.WriteLine($"{Mau} chay tren duong.");
    }

    // Phương thức abstract — không phải oto nào cũng chạy điện:
    public abstract void NapNhienLieuHoacSac();
}

Triển khai cụ thể


public class OtoDien : Oto
{
    public OtoDien(string mau) : base(mau) { }

    public override void NapNhienLieuHoacSac()
    {
        Console.WriteLine($"{Mau} sac bang dien.");
    }
}

public class OtoXang : Oto
{
    public OtoXang(string mau) : base(mau) { }

    public override void NapNhienLieuHoacSac()
    {
        Console.WriteLine($"{Mau} do xang.");
    }
}

Kết quả: abstraction nhiều tầng giúp thêm class, chức năng mới dễ dàng.

Minh họa: ví dụ hệ thống phân cấp class

Class Loại Abstract Cha Hành vi đặc biệt
PhuongTien Cơ bản - Phương thức abstract DiChuyen()
Oto Trung gian PhuongTien Abstract NapNhienLieuHoacSac()
OtoDien Cuối Không Oto Triển khai NapNhienLieuHoacSac()
OtoXang Cuối Không Oto Triển khai NapNhienLieuHoacSac()
Thuyen Cuối Không PhuongTien Triển khai DiChuyen()

4. Ứng dụng abstraction cho app dễ mở rộng

Lợi ích thực tế:

  1. Linh hoạt và dễ mở rộng: Bạn có thể thêm class mới (ví dụ loại động vật, phương tiện, nhân viên mới) mà không phải sửa code cũ. Vòng lặp/method sẽ tự động làm việc với object mới — miễn là nó implement method đã định trong abstraction.
  2. Giảm lặp code: Thuộc tính, method chung (ví dụ tên, logic cơ bản) chỉ định nghĩa một lần ở abstract class, class con tự động có luôn.
  3. Phổ biến trong framework lớn: Ví dụ, ASP.NET MVC có abstract controller gốc (ControllerBase), WinForms có abstract class Control cho mọi UI element. Nhờ vậy framework mở rộng mà không phá vỡ phần đã viết.

Dùng ở đâu?

  • Bất kỳ hệ thống lớn nào: app ngân hàng (nhân viên, giao dịch), game (entity, nhân vật), app doanh nghiệp (catalog, sản phẩm, user), mọi dữ liệu phân cấp.
  • Phỏng vấn kỹ thuật: Biết xây class dựa trên abstraction và giải thích hệ thống phân cấp là câu hỏi phổ biến khi phỏng vấn.
  • Kiến trúc phần mềm: abstraction giúp dựng "bộ khung" kiến trúc trước khi code logic — tiện cho teamwork, TDD (test-driven development) và bảo trì lâu dài.

5. Lỗi thường gặp khi xây hệ thống phân cấp

Lỗi #1: Quên implement method abstract.
Compiler sẽ không cho tạo instance class con nếu chưa implement hết member abstract của class cha. Luật bất thành văn: muốn cụ thể thì phải implement hết.

Lỗi #2: Cố gắng kế thừa nhiều abstract class.
C# không cho kế thừa nhiều class cùng lúc, kể cả đều là abstract. Đó là giới hạn của ngôn ngữ. Nếu muốn "kế thừa" hành vi từ nhiều nguồn — dùng interface nhé. Interface giống abstract class nhưng nhẹ hơn, không có code thực thi.

Lỗi #3: Không hiểu ý nghĩa abstract class trung gian.
Class này thường dùng để gom logic chung và ngăn tạo object "chưa đủ định nghĩa". Ví dụ, class Oto nên là abstract để không ai tạo "oto" chung chung mà không rõ là xăng, dầu hay điện.

Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION