Chuyển tới nội dung chính

Liskov Substitution Principle (LSP)

5.1 Lý thuyết và Sự phá vỡ Hợp đồng

Nguyên lý Thay thế Liskov (LSP) phát biểu rằng các đối tượng của lớp con phải có thể thay thế được các đối tượng của lớp cha mà không làm thay đổi tính đúng đắn của chương trình. Vi phạm LSP thường xảy ra khi chúng ta lạm dụng kế thừa (Inheritance) để tái sử dụng mã nguồn thay vì mô hình hóa mối quan hệ "IS-A" (Là một) thực sự.

5.2 Case Study: Hệ thống Kẻ thù và Tháp canh (The Turret Problem)

Một ví dụ kinh điển trong phát triển game là mối quan hệ giữa Enemy (Kẻ thù di động) và Turret (Tháp canh cố định).

Giả sử ta có lớp Enemy với phương thức ảo Move():

public class Enemy : MonoBehaviour {
public virtual void Move() {
// Logic di chuyển mặc định
transform.Translate(Vector3.forward * Time.deltaTime);
}
}

Khi tạo lớp Turret, vì Turret cũng là kẻ thù và có thể bắn, ta kế thừa từ Enemy. Tuy nhiên, Turret không thể di chuyển. Để xử lý, lập trình viên thường ghi đè phương thức Move và ném ra ngoại lệ hoặc để trống:

public class Turret : Enemy {
public override void Move() {
throw new System.NotImplementedException("Turret cannot move!");
}
}

Điều này vi phạm LSP nghiêm trọng. Nếu GameManager quản lý một danh sách List<Enemy> và gọi Move() cho tất cả phần tử, ứng dụng sẽ bị lỗi (crash) khi gặp Turret.

5.3 Giải pháp: Ưu tiên Composition (Thành phần) hơn Inheritance (Kế thừa)

Unity được xây dựng dựa trên triết lý "Composition over Inheritance". Để tuân thủ LSP, ta nên tránh tạo ra các cây kế thừa sâu và phức tạp. Thay vào đó, hãy sử dụng Interface hoặc Component rời rạc.

Tái cấu trúc bằng Interface:

Tách các hành vi thành các Interface nhỏ (kết hợp với ISP):

  • IDamageable: Có thể nhận sát thương.
  • IMoveable: Có thể di chuyển.
  • IAttackable: Có thể tấn công.

Lớp EnemySoldier sẽ thực thi cả IMoveable và IAttackable.

Lớp Turret sẽ chỉ thực thi IAttackable.

Hệ thống quản lý di chuyển (MovementSystem) sẽ chỉ hoạt động trên danh sách List<IMoveable>, đảm bảo rằng mọi đối tượng trong danh sách đều thực sự có khả năng di chuyển, loại bỏ hoàn toàn khả năng xảy ra lỗi runtime do vi phạm LSP.