CodeGym /Các khóa học /JAVA 25 SELF /Xây dựng hệ phân cấp lớp, ví dụ đời thực

Xây dựng hệ phân cấp lớp, ví dụ đời thực

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

1. Hệ phân cấp lớp là gì?

Trong lập trình (và không chỉ trong đó), hệ phân cấp là một cấu trúc dạng cây, nơi chúng ta có các thực thể “chung” ở đỉnh cây (các lớp cơ sở), và bên dưới là các thực thể cụ thể hơn (các lớp con). Trong Java, hệ phân cấp lớp được xây dựng bằng kế thừa: mỗi lớp con lại có thể là cha của các lớp con khác, v.v.

Tương tự:

  • Động vật (Animal) — là lớp tổng quát.
  • Động vật có vú (Mammal) — là trường hợp cụ thể của động vật.
  • Chó (Dog) — là trường hợp cụ thể của động vật có vú.
  • Còn “Sharik” cụ thể — chính là một đối tượng của lớp Dog.

Trong mã, nó trông như sau:

class Animal { }
class Mammal extends Animal { }
class Dog extends Mammal { }

Hệ phân cấp lớp cho phép mô tả các thuộc tính và hành vi chung “ở trên”, còn chi tiết — “ở dưới”. Điều này khiến mã logic hơn và loại bỏ sự trùng lặp.

Sơ đồ hệ phân cấp

Animal
├── Mammal
│   ├── Dog
│   └── Cat
└── Bird
    └── Sparrow

2. Cách xây dựng hệ phân cấp: logic và thực hành

Xác định phần chung và phần riêng

Quy tắc chính: lớp cơ sở phải chứa những gì đặc trưng cho tất cả hậu duệ của nó. Còn mọi thứ độc nhất — hãy đưa xuống các lớp con.

Ví dụ:

  • Tất cả động vật đều có thể thở và ăn — vậy các phương thức breathe()eat() nên nằm ở lớp Animal.
  • Chỉ có chim mới biết bay — vậy phương thức fly() sẽ ở lớp Bird, không phải ở Animal.
  • Chỉ chó mới biết sủa — phương thức bark() sẽ ở lớp Dog.

Ví dụ: Động vật

// Lớp cơ sở
class Animal {
    String name;

    Animal(String name) {
        this.name = name;
    }

    void eat() {
        System.out.println(name + " ăn.");
    }

    void makeSound() {
        System.out.println(name + " phát ra âm thanh.");
    }
}

// Lớp con: Động vật có vú
class Mammal extends Animal {
    Mammal(String name) {
        super(name);
    }

    void feedMilk() {
        System.out.println(name + " nuôi con bằng sữa.");
    }
}

// Lớp con: Chó
class Dog extends Mammal {
    Dog(String name) {
        super(name);
    }

    @Override
    void makeSound() {
        System.out.println(name + " sủa: Gâu gâu!");
    }

    void wagTail() {
        System.out.println(name + " vẫy đuôi.");
    }
}

// Lớp con: Mèo
class Cat extends Mammal {
    Cat(String name) {
        super(name);
    }

    @Override
    void makeSound() {
        System.out.println(name + " kêu: Meo!");
    }

    void purr() {
        System.out.println(name + " gừ gừ.");
    }
}

// Lớp con: Chim
class Bird extends Animal {
    Bird(String name) {
        super(name);
    }

    void fly() {
        System.out.println(name + " bay.");
    }

    @Override
    void makeSound() {
        System.out.println(name + " hót: Chíp chíp!");
    }
}

Gọi trong main:

public class ZooDemo {
    public static void main(String[] args) {
        Dog sharik = new Dog("Sharik");
        Cat murka = new Cat("Murka");
        Bird sparrow = new Bird("Chim sẻ");

        sharik.eat();        // Sharik ăn.
        sharik.makeSound();  // Sharik sủa: Gâu gâu!
        sharik.feedMilk();   // Sharik nuôi con bằng sữa.
        sharik.wagTail();    // Sharik vẫy đuôi.

        murka.eat();         // Murka ăn.
        murka.makeSound();   // Murka kêu: Meo!
        murka.feedMilk();    // Murka nuôi con bằng sữa.
        murka.purr();        // Murka gừ gừ.

        sparrow.eat();       // Chim sẻ ăn.
        sparrow.makeSound(); // Chim sẻ hót: Chíp chíp!
        sparrow.fly();       // Chim sẻ bay.
    }
}

Trực quan: cây lớp

Lớp Cha Đặc điểm
Animal
Object
name, eat(), makeSound()
Mammal
Animal
feedMilk()
Dog
Mammal
makeSound(), wagTail()
Cat
Mammal
makeSound(), purr()
Bird
Animal
fly(), makeSound()

3. Thêm ví dụ đời thực

Các hình hình học

Hệ phân cấp lớp rất phù hợp để mô hình hóa hình học.

// Lớp cơ sở
class Shape {
    void draw() {
        System.out.println("Vẽ hình.");
    }
}

// Hình tròn
class Circle extends Shape {
    double radius;

    Circle(double radius) {
        this.radius = radius;
    }

    @Override
    void draw() {
        System.out.println("Vẽ hình tròn có bán kính " + radius);
    }
}

// Hình chữ nhật
class Rectangle extends Shape {
    double width, height;

    Rectangle(double width, double height) {
        this.width = width;
        this.height = height;
    }

    @Override
    void draw() {
        System.out.println("Vẽ hình chữ nhật " + width + "x" + height);
    }
}

Sử dụng:

public class ShapeDemo {
    public static void main(String[] args) {
        Shape s1 = new Circle(5);
        Shape s2 = new Rectangle(3, 4);

        s1.draw(); // Vẽ hình tròn có bán kính 5.0
        s2.draw(); // Vẽ hình chữ nhật 3.0x4.0
    }
}

Phương tiện giao thông

class Vehicle {
    void move() {
        System.out.println("Phương tiện di chuyển.");
    }
}

class Car extends Vehicle {
    @Override
    void move() {
        System.out.println("Ô tô chạy trên đường.");
    }
}

class Bicycle extends Vehicle {
    @Override
    void move() {
        System.out.println("Người đi xe đạp đạp bàn đạp.");
    }
}

main:

Vehicle v1 = new Car();
Vehicle v2 = new Bicycle();

v1.move(); // Ô tô chạy trên đường.
v2.move(); // Người đi xe đạp đạp bàn đạp.

Người dùng

class User {
    String username;
    User(String username) { this.username = username; }
    void login() { System.out.println(username + " đã đăng nhập vào hệ thống."); }
}

class Admin extends User {
    Admin(String username) { super(username); }
    void deleteUser(String user) {
        System.out.println(username + " đã xóa người dùng " + user);
    }
}

class Customer extends User {
    Customer(String username) { super(username); }
    void buy() { System.out.println(username + " đã mua hàng."); }
}

4. Những lưu ý hữu ích

Đừng lạm dụng kế thừa. Kế thừa là công cụ cho quan hệ “is-a”. Nếu bạn muốn nói “Mèo là Động vật”, hãy dùng kế thừa. Còn nếu “Mèo có đuôi”, hãy dùng hợp thành (has-a).

Không tốt:

class Engine { /* ... */ }
class Car extends Engine { /* Ô tô là động cơ ư? Không, đây là hợp thành! */ }

Tốt:

class Car {
    Engine engine; // Ô tô có động cơ
}

Đừng tạo hệ phân cấp quá sâu. Cấp càng nhiều — càng khó bảo trì và hiểu mã. Thường 2–3 cấp là tối đa cho phần lớn bài toán.

Đừng tạo hệ phân cấp quá “phẳng”. Nếu bạn có 20 lớp đều kế thừa trực tiếp từ một lớp cơ sở, có lẽ nên xem lại kiến trúc.

Kế thừa “cho tiện”. Đôi khi ta muốn “mượn” phương thức hay trường mà không xem xét logic. Đó là con đường dẫn tới hỗn loạn! Nếu class Aclass B không liên quan về mặt logic, đừng làm class B extends A chỉ vì vài phương thức.

Vi phạm nguyên lý thay thế của Liskov. Nếu lớp con không thể được dùng thay cho lớp cha mà không gây bất ngờ — hệ phân cấp đã được xây sai.

Lặp lại mã. Nếu bạn nhận ra “ồ, phương thức này đang bị sao chép từ lớp này sang lớp khác”, có lẽ đã đến lúc đưa nó lên lớp cơ sở.

5. Các lỗi thường gặp khi xây dựng hệ phân cấp lớp

Lỗi số 1: Kế thừa mà không có logic “is-a”.
Nếu bạn dùng kế thừa chỉ để truy cập phương thức hoặc trường, chứ không phải vì lớp con thực sự “là” lớp cha, mã của bạn sẽ nhanh chóng thành mớ hỗn độn. Ví dụ, “Ô tô kế thừa Động cơ” — đó không phải is-a, mà là has-a.

Lỗi số 2: Bỏ qua đặc điểm riêng của lớp con.
Nếu mọi lớp con đều trông giống nhau và không thêm điều gì mới, có lẽ hệ phân cấp là không cần thiết — hãy dùng một lớp.

Lỗi số 3: Lặp lại mã trong các lớp con.
Nếu bạn sao chép cùng một hiện thực vào nhiều lớp con, hãy đưa nó lên lớp cha.

Lỗi số 4: Hệ phân cấp quá phức tạp hoặc quá sâu.
Hệ phân cấp nhiều tầng rất khó bảo trì và kiểm thử. Càng đơn giản — càng tốt!

Lỗi số 5: Ghi đè phương thức mà không có chú thích @Override.
Không có chú thích này, rất dễ sai chữ ký phương thức, và khi đó phương thức sẽ không được ghi đè, mà sẽ bị coi là mới. Luôn sử dụng @Override!

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