CodeGym /課程 /JAVA 25 SELF /建立類別階層,生活中的範例

建立類別階層,生活中的範例

JAVA 25 SELF
等級 17 , 課堂 3
開放

1. 什麼是類別階層?

在程式設計(以及其他領域)中,階層是一種樹狀結構:樹頂是較「共通」的實體(基底類別),往下則是更具體的實體(子類別)。在 Java 中,類別階層是藉由繼承建立的:每個子類別本身也可以成為其他子類別的父類別,依此類推。

類比:

  • 動物(Animal)是通用的類別。
  • 哺乳類(Mammal)是動物的一種特例。
  • 狗(Dog)是哺乳類的一種特例。
  • 而具體的「Sharik」則是 Dog 類別的一個物件。

在程式碼中如下:

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

類別階層讓我們能在「上層」描述共通屬性與行為,在「下層」保留細節。這使程式碼更有邏輯,並可避免重複。

階層示意圖

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

2. 如何構建階層:邏輯與實務

辨識共通與特定

關鍵準則:基底類別應包含所有後代都具備的特徵;而獨特的部分應下放到子類別。

範例:

  • 所有動物都會呼吸與進食——因此方法 breathe()eat() 應該在類別 Animal 中。
  • 只有鳥會飛——因此方法 fly() 應該在類別 Bird,而不是在 Animal
  • 只有狗會叫——方法 bark() 應該在類別 Dog

範例:動物

// 基底類別
class Animal {
    String name;

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

    void eat() {
        System.out.println(name + " 在吃東西。");
    }

    void makeSound() {
        System.out.println(name + " 發出聲音。");
    }
}

// 子類別:哺乳類
class Mammal extends Animal {
    Mammal(String name) {
        super(name);
    }

    void feedMilk() {
        System.out.println(name + " 用母乳餵幼崽。");
    }
}

// 子類別:狗
class Dog extends Mammal {
    Dog(String name) {
        super(name);
    }

    @Override
    void makeSound() {
        System.out.println(name + " 在叫:汪汪!");
    }

    void wagTail() {
        System.out.println(name + " 搖尾巴。");
    }
}

// 子類別:貓
class Cat extends Mammal {
    Cat(String name) {
        super(name);
    }

    @Override
    void makeSound() {
        System.out.println(name + " 在喵叫:喵!");
    }

    void purr() {
        System.out.println(name + " 發出呼嚕聲。");
    }
}

// 子類別:鳥
class Bird extends Animal {
    Bird(String name) {
        super(name);
    }

    void fly() {
        System.out.println(name + " 在飛。");
    }

    @Override
    void makeSound() {
        System.out.println(name + " 在啁啾:啾啾!");
    }
}

在 main 中的呼叫:

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

        sharik.eat();        // Sharik 在吃東西。
        sharik.makeSound();  // Sharik 在叫:汪汪!
        sharik.feedMilk();   // Sharik 用母乳餵幼崽。
        sharik.wagTail();    // Sharik 搖尾巴。

        murka.eat();         // Murka 在吃東西。
        murka.makeSound();   // Murka 在喵叫:喵!
        murka.feedMilk();    // Murka 用母乳餵幼崽。
        murka.purr();        // Murka 發出呼嚕聲。

        sparrow.eat();       // Vorobey 在吃東西。
        sparrow.makeSound(); // Vorobey 在啁啾:啾啾!
        sparrow.fly();       // Vorobey 在飛。
    }
}

視覺化:類別樹

類別 父類別 特點
Animal
Object
name, eat(), makeSound()
Mammal
Animal
feedMilk()
Dog
Mammal
makeSound(), wagTail()
Cat
Mammal
makeSound(), purr()
Bird
Animal
fly(), makeSound()

3. 更多生活中的例子

幾何圖形

類別階層非常適合用來建模幾何圖形。

// 基底類別
class Shape {
    void draw() {
        System.out.println("繪製圖形。");
    }
}

// 圓形
class Circle extends Shape {
    double radius;

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

    @Override
    void draw() {
        System.out.println("繪製半徑為 " + radius + " 的圓");
    }
}

// 長方形
class Rectangle extends Shape {
    double width, height;

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

    @Override
    void draw() {
        System.out.println("繪製長方形 " + width + "x" + height);
    }
}

使用方式:

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

        s1.draw(); // 繪製半徑為 5.0 的圓
        s2.draw(); // 繪製長方形 3.0x4.0
    }
}

交通工具

class Vehicle {
    void move() {
        System.out.println("交通工具正在移動。");
    }
}

class Car extends Vehicle {
    @Override
    void move() {
        System.out.println("汽車在道路上行駛。");
    }
}

class Bicycle extends Vehicle {
    @Override
    void move() {
        System.out.println("自行車騎士在踩踏板。");
    }
}

main:

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

v1.move(); // 汽車在道路上行駛。
v2.move(); // 自行車騎士在踩踏板。

使用者

class User {
    String username;
    User(String username) { this.username = username; }
    void login() { System.out.println(username + " 已登入系統。"); }
}

class Admin extends User {
    Admin(String username) { super(username); }
    void deleteUser(String user) {
        System.out.println(username + " 刪除了使用者 " + user);
    }
}

class Customer extends User {
    Customer(String username) { super(username); }
    void buy() { System.out.println(username + " 完成了一筆購買。"); }
}

4. 實用細節

不要濫用繼承。 繼承是用於「is-a」關係的工具。如果你要表達「貓是動物」,請使用繼承;如果是「貓擁有尾巴」,請使用組合(has-a)。

錯誤示範:

class Engine { /* ... */ }
class Car extends Engine { /* 車就是引擎嗎?不是,這是組合! */ }

正確示範:

class Car {
    Engine engine; // 汽車包含引擎
}

不要建立過深的階層。 層級越多,維護與理解就越困難。多數情況下 2–3 層已足夠。

也不要讓階層過於「扁平」。 如果有 20 個類別都直接繼承同一個基底類別,可能該重新檢視架構。

為了「方便」而繼承。 有時會想為了「偷」幾個方法或欄位而不顧語意。這會走向混亂!如果 class Aclass B 在語意上無關,就不要讓 class B extends A 只為了那幾個方法。

違反 Liskov 置換原則。 若子類別無法在不出現意外的情況下被當作父類別替換使用——代表階層設計不正確。

程式碼重複。 如果你發現自己在想「咦,這個方法在好幾個類別都在複製」,也許該把它抽到基底類別。

5. 建立類別階層時的常見錯誤

錯誤 №1:缺乏「is-a」語意的繼承。
如果你僅為了取得方法或欄位而使用繼承,而不是因為子類別確實「是」父類別的一種,你的程式碼很快會變得混亂。例如,「汽車繼承引擎」不是 is-a,而是 has-a

錯誤 №2:忽略子類別的獨特性。
若所有子類別看起來都一樣、沒有新增任何功能,可能根本不需要階層——使用單一類別即可。

錯誤 №3:在子類別中重複程式碼。
若你在多個子類別中複製相同實作,應將其上移到父類別。

錯誤 №4:階層過於複雜或過深。
多層級的階層很難維護與測試;越簡單越好!

錯誤 №5:覆寫方法時未使用 @Override 標註。
沒有標註很容易在方法簽章上出錯,導致方法沒有被正確覆寫而是被視為新的方法。請務必使用 @Override

留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION