『Enumとてもつらい、でも負けない』は開放閉鎖の原則を意識すると解決できるかもしれない
環境
code:sh
$swift --version
swift-driver version: 1.87.1 Apple Swift version 5.9 (swiftlang-5.9.0.128.108 clang-1500.0.40.1)
Target: arm64-apple-macosx14.0
モチベーション
考えをまとめる良い題材だった
/icons/hr.icon
問題1: 既存のif文が偶発的に意図しない方に倒れる
これはSwiftでも同じ問題がある
純粋なif文による比較と、if caseによる比較どちらもelseに追加したcase文の処理が流れてしまう。elseが無ければ処理は実行されない
問題2: switch文に至っては「どちらでもない」で処理不発に
Javaのswitch文は網羅性チェックをしないが、Swiftは網羅性チェックがあるため大丈夫
caseを追加しても網羅性チェックをしないのでコンパイルが通ってしまう
Swiftは網羅性チェックがある(= switchでcaseの網羅性が担保されていないとコンパイルが通らない)
対策
1. 分岐条件をEnumに持たせる
これは記事中にも書いてあるが処理が増えるごとにenumが肥大化する
→ 全ての処理に対して全てのcase数文処理を書く必要があるため
2. switch文でなくswitch式を使う
Swift 5.9でも登場したswitch式を使うやり方
Javaだとswitch文と異なり網羅性チェックがはたらくみたいで、その点で紹介されていた
網羅性チェックの有無の点で問題2を解決できる
開放閉鎖の原則
SOLID原則のO(open/closed principle)
software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification.(→ソフトウェアの実体(クラス、モジュール、関数など)は、拡張に対して開かれているべきであり、修正に対して閉じていなければならない)
この原則で記事中の例を見ると、caseを増やすとそのenumを使って処理をしている箇所全てに新しいcaseの対応が必要になって、拡張に対して開かれていない状況になる
この原則に従って考えると、可変する箇所はinterface(Swiftでいうprotocol)を切り、具象型を増やすやり方を取る。実際にEmployeeを使う箇所はinterfaceとして扱えば良いのでcaseの増加によって至る所に修正が必要にはならない。
code:swift
protocol Employee {
var useMonthlySalary: Bool { get }
}
struct FullTime: Employee {
var useMonthlySalary: Bool = true
}
struct PartTime: Employee {
var useMonthlySalary: Bool = false
}
配偶者控除の有無などのようなFullTimeやPartTime内でも一律で定まらない値を表現したい時はイニシャライズ時に外から渡せば良さそう
code:swift
protocol Employee {
var useMonthlySalary: Bool { get }
var applyExemptionForSpouse: Bool { get }
}
struct FullTime: Employee {
var useMonthlySalary: Bool = true
var applyExemptionForSpouse: Bool
}
struct PartTime: Employee {
var useMonthlySalary: Bool = false
var applyExemptionForSpouse: Bool
}
let andy = FullTime(applyExemptionForSpouse: false)