前陣子接收到很有趣的程式碼,基本上就是一個很大的 enum 有多達 30 個以上的 case,還有一個 4000 行 Factory 類別,Factory 裡面都是依照 enum 做各種處理,當初這類別相當複雜,有些處理會呼叫 Api、有些讀取 DB、每個 case 的步驟也都不相同,但通通都在這個類別處理,導致程式碼快 4000 行。

下面先簡化成三個 case 的 enum

CakeType.swift
1
2
3
4
5
enum CakeType{
case strawberryCake
case cheeseCake
case chocolateCake
}
CakeFactory.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class CakeFactory{

/// 取得價格
func getPrice(cakeType: CakeType) -> Double{
switch cakeType{
case .strawberryCake: return 100.0
case .cheeseCake: return 85.0
case .chocolateCake: return 120.0
}
}

/// 取得成分資料
func getIngredientData(cakeType: CakeType) -> IngredientData{
switch cakeType{
case .strawberryCake: // ...
case .cheeseCake: // ...
case .chocolateCake: // ...
}
}
}

其實可以做一個簡單的重構,聽過一種說法是”所有 switch case 都可以用設計模式來取代!”,
所以只要看到有大量 switch case 的判斷,都可以想想有沒有其他解決方式。
對我來說使用 switch case 有三個小缺點

  1. 無法讓權責分明,行數很多,此方法或類別處理兩條以上的流程 ( ex. A 流程, B 流程 …. )
  2. 未來新增 case 時,只要有用到 switch case 就要每個地方都去修正 ( ex. 新增 C 流程 )
  3. 如果想要抽換 A 流程作法,是很難做到的 ( ex. A 流程改成 A-1 流程或是 A-2 流程 )

重構總共需要三步驟:

  1. 抽象化你的 enum case

    1
    2
    3
    4
    protocol Cake{
    var price: Double { get }
    func getIngredientData() -> IngredientData
    }
  2. 實作多個 Cake

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    struct StrawberryCake: Cake{
    var price: Double {
    return 100.0
    }
    func getIngredientData() -> IngredientData {
    return IngredientData() // ... 很多步驟省略
    }
    }
    struct CheeseCake: Cake{
    var price: Double {
    return 85.0
    }
    func getIngredientData() -> IngredientData {
    return IngredientData() // ... 很多步驟省略
    }
    }
    struct ChocolateCake: Cake{
    var price: Double {
    return 120.0
    }
    func getIngredientData() -> IngredientData {
    return IngredientData() // ... 很多步驟省略
    }
    }
  3. 修改 enum,方便可以使用它取得 Cake 的實作,必要時也可以動態修改 Cake 的實作

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    enum CakeType{
    case strawberryCake
    case cheeseCake
    case chocolateCake
    static var allCake: [CakeType: Cake] = [
    .strawberryCake: StrawberryCake(),
    .cheeseCake: CheeseCake(),
    .chocolateCake: ChocolateCake()
    ]
    func getCake() -> Cake?{
    return CakeType.allCake[self]
    }
    }

使用方式:

1
print(CakeType.strawberryCake.getCake()?.price)

結論來講,這種作法已經可以將 30 個以上的 case,分散到 30 個物件(檔案)去實作,找起 Bug 來方便許多;
如果要新增 case 也不用在每個 switch case 補上去,因為已經沒有任何 switch case 了,這種抽象化的做法是非常常見的。
但是當然不是所有狀況都需要加上抽象化,這可能會變成 Over Design,需要對專案和需求做進一步的評估才對。


延伸想法:

之前做過另一種重構 enum case 的方式是使用 static 屬性!

1
2
3
4
5
6
7
struct Cake{
var price: Double
static var strawberryCake = Cake(price: 100.0)
static var cheeseCake = Cake(price: 85.0)
static var chocolateCake = Cake(price: 120.0)
}
print(Cake.strawberryCake.price)