Menu
CoddyTech

Ưu tiên composition hơn kế thừa

Thuộc phần Lập trình hướng đối tượng trong hành trình C# của Coddy. Bài học 41 trên 70.

Kế thừa tạo ra sự phụ thuộc chặt chẽ giữa các lớp. Khi Car kế thừa từ Vehicle, mọi thay đổi đối với Vehicle đều có thể khiến Car gặp lỗi. Kết hợp mang đến một lựa chọn linh hoạt hơn: thay vì kế thừa hành vi, một lớp chứa các đối tượng cung cấp hành vi đó.

Hãy hình dung một nhân vật trong trò chơi có thể di chuyển và tấn công. Với kế thừa (inheritance), bạn có thể tạo ra một hệ thống phân cấp phức tạp. Với cách kết hợp các thành phần (composition), bạn chỉ cần cung cấp cho nhân vật những thành phần cần thiết:

public class MovementBehavior
{
    public void Move() => Console.WriteLine("Moving...");
}

public class AttackBehavior
{
    public void Attack() => Console.WriteLine("Attacking!");
}

public class Character
{
    private MovementBehavior movement = new MovementBehavior();
    private AttackBehavior attack = new AttackBehavior();
    
    public void Move() => movement.Move();
    public void Attack() => attack.Attack();
}

Điểm khác biệt chính nằm ở mối quan hệ. Kế thừa biểu thị quan hệ "là một" (Dog là một Animal), còn kết hợp biểu thị quan hệ "có" (Character có hành vi di chuyển). Kết hợp cho phép bạn thay đổi hành vi khi chương trình đang chạy, tự do kết hợp các khả năng và tránh vấn đề lớp cơ sở dễ bị lỗi.

Dùng kế thừa khi có mối quan hệ phân cấp thực sự. Chọn kết hợp khi bạn cần sự linh hoạt, khi các hành vi có thể được chia sẻ giữa những lớp không liên quan hoặc khi bạn muốn thay đổi hành vi một cách linh hoạt.

challenge icon

Thử thách

Dễ

Hãy xây dựng một hệ thống thông báo để minh họa sức mạnh của việc kết hợp thành phần thay vì kế thừa. Thay vì tạo một hệ thống phân cấp phức tạp gồm các lớp thông báo, bạn sẽ tạo NotificationSender bằng cách gắn vào đó các thành phần hành vi có thể thay thế cho nhau.

Bạn sẽ tổ chức code trong ba tệp:

  • Behaviors.cs: Tạo hai lớp hành vi trong không gian tên Notifications để xử lý các khía cạnh khác nhau của việc gửi thông báo:
    • FormattingBehavior: có phương thức Format(string message) trả về thông báo được bao quanh bởi [ALERT] ở đầu và [END] ở cuối
    • DeliveryBehavior: có phương thức Deliver(string channel, string message) trả về chuỗi theo định dạng Sending via {channel}: {message}
  • NotificationSender.cs: Tạo lớp NotificationSender trong không gian tên Notifications, dùng cách kết hợp thành phần để phối hợp các hành vi. Lớp gửi thông báo của bạn cần:
    • Có các thực thể của cả FormattingBehavior và DeliveryBehavior dưới dạng các trường riêng tư
    • Có thuộc tính Channel (chuỗi) lưu tên kênh gửi
    • Có hàm khởi tạo nhận tên kênh
    • Có phương thức Send(string message) trước tiên định dạng thông báo bằng hành vi định dạng, sau đó gửi thông báo bằng hành vi gửi và trả về kết quả cuối cùng
  • Program.cs: Trong tệp chính, hãy tạo NotificationSender với kênh lấy từ đầu vào, sau đó gửi thông báo lấy từ đầu vào. In kết quả để cho thấy các hành vi được kết hợp hoạt động cùng nhau như thế nào.

Bạn sẽ nhận hai đầu vào:

  • Tên kênh gửi, chẳng hạn như Email, SMS
  • Thông báo cần gửi

In kết quả khi gọi Send() trên lớp gửi thông báo của bạn.

Ví dụ, nếu đầu vào là Email và Server is down, đầu ra sẽ là:

Sending via Email: [ALERT] Server is down [END]

Lưu ý rằng NotificationSender không kế thừa từ lớp nào cả, nó chỉ “có” các hành vi định dạng và gửi thông báo. Cách kết hợp thành phần này giúp bạn dễ dàng thay thế một trong hai hành vi mà không cần thay đổi lớp gửi thông báo, hoặc tái sử dụng các hành vi này trong những lớp hoàn toàn khác!

Tự làm thử

using System;
using Notifications;

class Program
{
    public static void Main(string[] args)
    {
        // Đọc đầu vào
        string channel = Console.ReadLine();
        string message = Console.ReadLine();

        // TODO: Tạo NotificationSender với channel
        
        // TODO: Gửi message và in ra kết quả
        
    }
}
quiz iconTự kiểm tra

Bài học này có một bài trắc nghiệm ngắn. Bắt đầu bài học để trả lời và theo dõi tiến độ.

Tất cả bài học trong Lập trình hướng đối tượng

Tự luyện tập: Trình biên dịch C# online