iT邦幫忙

2024 iThome 鐵人賽

DAY 1
0
Software Development

設計原則與設計模式系列 第 1

策略模式與單一職責原則

  • 分享至 

  • xImage
  •  

單一職責原則,英文稱作 Single Responsibility Principle,簡稱 SRP,是軟體設計中的一個重要原則。該原則強調每個類別(或每個方法)應該只負責一項職責,或服務於一個角色,並且這項職責必須完全封裝於類別或方法當中。透過「將不同的職責分離到不同的位置」這一行為,雖然實現該原則時,容易產生較高的類別數目,但這麼做不僅可以提高類別的內聚性,還可以改善系統的可維護性、可讀性、和可測試性,使程式更加易讀、易測試、和易於管理。

舉例來說:假設我們正在撰寫一個銀行行業的業務邏輯,邏輯當中帶有一個提款(withdraw)的方法,該方法負責處理用戶的提款請求。如果該銀行目前只針對客戶簡單區分為普通客戶與 VIP 客戶,其中普通客戶的單次提醒上限為 1000,VIP 沒有提領上限,且可以稍微預支提領金額(為當前戶頭金額再多 5%),程式可能會寫得像下面這個樣子:

class BankAccount {
    public double withdraw(double amount) {
        // 普通客戶
        if (!User.isVIP) {
            if (User.balance < amount) {
                throw new IllegalArgumentException("Insufficient balance");
            }
            
            if(amount > BankAccount.REGULAR_MAX_WITHDRAWAL) {
                throw new IllegalArgumentException("Exceeds maximum withdrawal amount");
            }
        }
        // VIP客戶
        else {
            double vip_max_withdrawal = User.balance * BankAccount.OVERFLOT_PERCENTAGE;
            if (amount > vip_max_withdrawal) {
                throw new IllegalArgumentException("Exceeds maximum withdrawal amount");
            }
        }

        User.balance -= amount;
        return User.balance;
    }
}

但如果我們複雜化銀行的業務邏輯:現在銀行將客戶分為普通客戶、白金客戶、鑽石客戶和 VIP 客戶 ...等 4 個等級,且每種客戶類型都有不同的提款規則和權限,包含但不限於以下條件:普通客戶的單次提款上限為 1,000、每日提款總額不得超過 5,000、不允許透支、且超過 500 的提款需要接收手機驗證碼;白金客戶的單次提款上限為 5,000、每日提款總額不得超過 50,000、允許小額透支(最多為賬戶餘額的 2%),但透支需於月底要支付總透支的 1% 作為手續費,也同樣需要接收手機驗證碼(2,000 元);

鑽石客戶的單次提款上限為 20,000、每日提款總額不得超過 500,000、允許最多賬戶餘額的 5% 作為透支金額,同樣需要於月底支付總透支的 0.5% 作為手續費,超過 10,000 的大額提款一定會有簡訊通知,無法關閉;而 VIP 客戶無單次提款上限、無每日提款總額不設限,但超過 500,000 需特別申請,需要透過客戶專屬的經理確認。允許大額透支,依照 VIP 的不同等級可再細分為賬戶餘額的 10% ~ 20% 作為最高上限金額。

且對於特定的卡號(不一定是哪個等級),有著額外的業務邏輯需要處理(被凍結的、行員內部的、需要被監控的、或者是大戶贈與子女的特別卡 ...等)。不難想像當這些業務邏輯全部都放在 withdraw() 裡面之後,該方法將會變得非常的生人勿近 ...。因此,對於這樣的情況(一個 withdraw() 需要為不同等級的、不同狀況的、不同複雜程度的客戶所負責),一個相對簡單的解決方法,便是利用 SRP(單一職責原則)完成程式上的「分而治之」:

interface WithdrawStrategy {
    double withdraw(double amount) throws IllegalArgumentException;
}

// 普通客戶提款策略
class RegularWithdrawStrategy implements WithdrawStrategy {
    private static final double MAX_SINGLE_WITHDRAWAL = 1000;
    private static final double MAX_DAILY_WITHDRAWAL = 5000;
    private static final double SMS_VERIFICATION_THRESHOLD = 500;

    @Override
    public double withdraw(double amount) throws IllegalArgumentException {
        if (amount > MAX_SINGLE_WITHDRAWAL)
            throw new IllegalArgumentException("超出單次提款上限");
        if (User.getDailyWithdrawalTotal() + amount > MAX_DAILY_WITHDRAWAL)
            throw new IllegalArgumentException("超出每日提款總額限制");
        if (User.getBalance() < amount)
            throw new IllegalArgumentException("餘額不足");
  
        if (amount > SMS_VERIFICATION_THRESHOLD) {
            verifySMS();
        }
        User.setBalance(User.getBalance() - amount);
        User.addToDailyWithdrawalTotal(amount);
        return User.getBalance();
    }
}

// 白金客戶提款策略
class PlatinumWithdrawStrategy implements WithdrawStrategy {
    @Override
    public double withdraw(double amount) throws IllegalArgumentException {
        // 白金客戶的提款邏輯
    }
}

// 鑽石客戶提款策略
class DiamondWithdrawStrategy implements WithdrawStrategy {
    // 鑽石客戶的提款邏輯
}

// 其他等級、特殊卡號的提款邏輯 ...
class User {
    private WithdrawStrategy withdrawStrategy;

    public User(String name, double initialBalance, WithdrawStrategy strategy) {
        // 建構子 ...
    }

    public double withdraw(double amount) throws IllegalArgumentException {
        return withdrawStrategy.withdraw(this, amount);
    }

    // Getters and setters
    public double getDailyWithdrawalTotal() {
        return dailyWithdrawalTotal;
    }
    public void setWithdrawStrategy(WithdrawStrategy strategy) {
        this.withdrawStrategy = strategy;
    }
    
    // 其他 getter setter ...
class Bank {
    public double withdraw(User user, double amount) throws IllegalArgumentException {
        // 使用用戶的提款策略執行提款操作
        return user.getWithdrawStrategy().withdraw(user, amount);
    }
    
    // 其他關於銀行的業務邏輯 ...
}

在這裡我們定義了一個 WithdrawStrategy 的接口,且該接口包含一個 withdraw() 方法。不同客戶等級的提款策略,只要各自獨立為一個類別,並實例化 WithdrawStrategy 的接口、完成各自的 withdraw() 邏輯,再讓 User 根據不同的等級,持有不同種類的 WithdrawStrategy,就可以簡化 Bank.withdraw() 方法、以及 withdraw() 本身的內容了。這便是利用了單一職責原則達到的程式改善。而這種「相對於把所有的可能性擠在一個方法內,對『方法』本身進行一次抽象,並實例化不同的『方法』類別,以達到不同方法可以根據情境進行實裝、調用」的行為,也一種常見的設計模式——策略模式的撰寫邏輯。


系列文
設計原則與設計模式1
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言