CodeGym /課程 /JAVA 25 SELF /在實際任務中構建抽象的範例

在實際任務中構建抽象的範例

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

1. 真實應用中的抽象:為什麼需要它

在前面的課程中,我們已經認識了抽象的概念並看過簡單範例。現在來看看這個方法在更真實的任務中是如何運作的。 在實際專案中,幾乎總會面對不同但相似的物件。例如,不同的支付方式、不同的交通工具、圖形編輯器中的不同圖形。若不運用抽象,程式碼很快就會變成一堆「if-else」和重複貼上的片段。有了抽象——一切更嚴謹、漂亮,而且最重要的是,便於擴展與維護。

抽象可以:

  • 隱藏實作細節:透過共同介面與物件互動,不必關心其內部實作。
  • 避免程式碼重複:共同行為與欄位抽取到基底類別。
  • 容易擴充系統:新增新的物件類型不需要重寫舊程式碼。
  • 讓程式碼更靈活:可在不改動其餘系統的情況下替換實作。

讓我們從不同領域挑幾個例子來看看。

2. 範例 1:支付系統

任務說明

假設你在為一個網路商店撰寫模組。你的任務是實作不同類型的支付處理:信用卡、PayPal、加密貨幣。它們都需要「處理付款」,但每種方式的細節各不相同。

抽象:Payment 類別

public abstract class Payment {
    protected double amount;

    public Payment(double amount) {
        this.amount = amount;
    }

    // 抽象方法:如何具體處理付款 — 由子類決定
    public abstract void process();

    // 所有支付方式的通用方法
    public void printAmount() {
        System.out.println("付款金額: " + amount + " 盧布");
    }
}

具體實作

public class CreditCardPayment extends Payment {
    private String cardNumber;

    public CreditCardPayment(double amount, String cardNumber) {
        super(amount);
        this.cardNumber = cardNumber;
    }

    @Override
    public void process() {
        System.out.println("處理信用卡付款: " + cardNumber);
        // 這裡本可以與銀行整合 :)
    }
}
public class PaypalPayment extends Payment {
    private String email;

    public PaypalPayment(double amount, String email) {
        super(amount);
        this.email = email;
    }

    @Override
    public void process() {
        System.out.println("處理 PayPal 付款,帳號: " + email);
        // 而這裡 — 呼叫 PayPal API
    }
}
public class CryptoPayment extends Payment {
    private String walletAddress;

    public CryptoPayment(double amount, String walletAddress) {
        super(amount);
        this.walletAddress = walletAddress;
    }

    @Override
    public void process() {
        System.out.println("處理加密貨幣付款,錢包位址: " + walletAddress);
        // 這裡本可以有區塊鏈魔法
    }
}

抽象的使用

import java.util.*;

public class PaymentDemo {
    public static void main(String[] args) {
        List<Payment> payments = new ArrayList<>();
        payments.add(new CreditCardPayment(1500.0, "1234 5678 9012 3456"));
        payments.add(new PaypalPayment(500.0, "user@example.com"));
        payments.add(new CryptoPayment(0.05, "0xABCD..."));

        for (Payment payment : payments) {
            payment.printAmount();
            payment.process();
            System.out.println("---");
        }
    }
}

執行結果:

付款金額: 1500.0 盧布
處理信用卡付款: 1234 5678 9012 3456
---
付款金額: 500.0 盧布
處理 PayPal 付款,帳號: user@example.com
---
付款金額: 0.05 盧布
處理加密貨幣付款,錢包位址: 0xABCD...
---

優點:

  • 可以新增新的支付方式而不需修改舊程式碼(例如 Apple Pay)。
  • 處理支付的程式碼不依賴其具體型別。
  • 共用邏輯(例如透過 printAmount() 列印金額)只需在一處實作。

3. 範例 2:交通工具

任務說明

在遊戲或模擬中,你有不同種類的交通工具:汽車、自行車、火車。它們都會「移動」,但方式各不相同。有些需要加油,有些則不需要。

抽象:Transport 類別

public abstract class Transport {
    protected String name;

    public Transport(String name) {
        this.name = name;
    }

    public abstract void move();

    // 並非所有交通工具都需要加油,預設為不需要
    public void fuelUp() {
        System.out.println(name + ": 不需要加油。");
    }
}

具體實作

public class Car extends Transport {
    public Car(String name) {
        super(name);
    }

    @Override
    public void move() {
        System.out.println(name + " 在道路上行駛。");
    }

    @Override
    public void fuelUp() {
        System.out.println(name + ": 加汽油。");
    }
}
public class Bicycle extends Transport {
    public Bicycle(String name) {
        super(name);
    }

    @Override
    public void move() {
        System.out.println(name + " 踩踏板前進。");
    }
    // 不覆寫 fuelUp — 自行車不需要加油
}
public class Train extends Transport {
    public Train(String name) {
        super(name);
    }

    @Override
    public void move() {
        System.out.println(name + " 在鐵軌上飛馳。");
    }

    @Override
    public void fuelUp() {
        System.out.println(name + ": 補給柴油或電力。");
    }
}

抽象的使用

import java.util.*;

public class TransportDemo {
    public static void main(String[] args) {
        List<Transport> vehicles = Arrays.asList(
            new Car("Toyota"),
            new Bicycle("Stels"),
            new Train("Sapsan")
        );

        for (Transport t : vehicles) {
            t.move();
            t.fuelUp();
            System.out.println("---");
        }
    }
}

執行結果:

Toyota 在道路上行駛。
Toyota: 加汽油。
---
Stels 踩踏板前進。
Stels: 不需要加油。
---
Sapsan 在鐵軌上飛馳。
Sapsan: 補給柴油或電力。
---

優點:

  • 可以用同一種方式處理任何交通工具,而不必檢查其型別。
  • 容易新增新的交通工具(例如電動滑板車)。

4. 範例 3:圖形編輯器

任務說明

你要撰寫一個迷你圖形編輯器。當中有直線、橢圓形、多邊形——它們都是「圖形」,可以被繪製並調整尺寸,而且每種圖形會以自己的方式實作這些操作。

抽象:Figure 類別

public abstract class Figure {
    protected String color = "black";

    public abstract void draw();

    public abstract void resize(double factor);

    public void setColor(String color) {
        this.color = color;
    }
}

具體實作

public class Line extends Figure {
    private double length;

    public Line(double length) {
        this.length = length;
    }

    @Override
    public void draw() {
        System.out.println("繪製長度為 " + length + " 、顏色為 " + color + " 的線條");
    }

    @Override
    public void resize(double factor) {
        length *= factor;
        System.out.println("線條的新長度: " + length);
    }
}
public class Ellipse extends Figure {
    private double a, b;

    public Ellipse(double a, double b) {
        this.a = a;
        this.b = b;
    }

    @Override
    public void draw() {
        System.out.println("繪製軸長 " + a + " 與 " + b + " 、顏色為 " + color + " 的橢圓");
    }

    @Override
    public void resize(double factor) {
        a *= factor;
        b *= factor;
        System.out.println("橢圓的新尺寸: " + a + " x " + b);
    }
}
public class Polygon extends Figure {
    private int sides;

    public Polygon(int sides) {
        this.sides = sides;
    }

    @Override
    public void draw() {
        System.out.println("繪製具有 " + sides + " 條邊、顏色為 " + color + " 的多邊形");
    }

    @Override
    public void resize(double factor) {
        System.out.println("將具有 " + sides + " 條邊的多邊形縮放為 " + factor);
    }
}

抽象的使用

import java.util.*;

public class EditorDemo {
    public static void main(String[] args) {
        List<Figure> figures = new ArrayList<>();
        figures.add(new Line(10));
        figures.add(new Ellipse(5, 3));
        figures.add(new Polygon(6));

        for (Figure f : figures) {
            f.setColor("green");
            f.draw();
            f.resize(2);
            System.out.println("---");
        }
    }
}

執行結果:

繪製長度為 10.0 、顏色為 green 的線條
線條的新長度: 20.0
---
繪製軸長 5.0 與 3.0 、顏色為 green 的橢圓
橢圓的新尺寸: 10.0 x 6.0
---
繪製具有 6 條邊、顏色為 green 的多邊形
將具有 6 條邊的多邊形縮放為 2.0
---

優點:

  • 可以將所有圖形放在同一個清單中並以相同方式處理。
  • 容易新增新的圖形(例如星形或愛心)。
  • 共用方法(例如透過 setColor() 設定顏色)只需實作一次。

5. 抽象如何幫助簡化程式碼

以上每個範例都有共同的模式:

  • 基底抽象類別 定義契約(物件能做什麼)。
  • 具體子類 負責實作細節。
  • 使用抽象的程式碼 不依賴物件型別,使系統更靈活且可擴充。

方法比較表

沒有抽象(if-else 具有抽象(OOP)
大量依賴型別的條件 新型別 — 需要改動程式碼
邏輯重複 邏輯集中在一處
難以擴充 新增 — 容易
難以測試 容易替換實作

6. 設計抽象時的常見錯誤

錯誤 1:為了抽象而抽象。
如果你只有一種物件類型,且不打算擴充——就不需要抽象類別。不要無故複雜化程式碼。

錯誤 2:抽象過於籠統。
如果基底類別過於「模糊」,子類可能除了名字外毫無共通點。例如,拿「Thing」當作萬用抽象。這會讓維護與理解變得困難。

錯誤 3:在子類中重複程式碼。
如果所有子類對某個方法的實作都相同,就應該把它上移到基底類別(改為非抽象)。

錯誤 4:違反「由一般到特殊」的原則。
如果抽象類別出現只對某個子類才需要的細節——代表抽象選錯了。

錯誤 5:忘了實作抽象方法。
如果沒有在子類中實作所有抽象方法,編譯器會強制將該類別也標記為抽象。有時這會讓人意外 :)

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