条件付きのロジックをStrategyパターンで置換する
コードが複雑になる要因の一つとしてif文やswitch文による条件付きロジックがあります。以下のような給与を計算する実装例を考えてみます。(employeeType
が文字列、という話はおいておきます)
switch
文があると呼び出し側の実装が複雑になります。switch
文を呼び出し元の実装からリファクタリングします。
- main.go
package main import "fmt" func main() { employeeType := "engineer" var amount int switch employeeType { case "engineer": amount = 1 case "salesman": amount = 10 default: amount = -1 } fmt.Println("amount", amount) }
Strategyパターン
呼び出し側のロジックを、適切なクラスのメソッドとして実装して、呼び出し側の実装をシンプルにします。デザインパターンのStrategyパターンを導入して、strcut
のメソッドとしてアルゴリズムを実装します。Strategyパターンを導入するとアルゴリズムを任意に差し替えることができます。employeeType
が追加になった場合には、呼び出し元の実装には変更は不要です。変更の範囲を局所化できます。今回の場合は employeeType
が engineer
と salesman
の2種類の計算ロジックがあれば十分です。
実装上のポイントは、アルゴリズムを実装した構造体のメソッドに移譲して呼び出すということです。NewEmployee
の値を例えばJavaのSpringを使っている場合はDIコンテナから取得する、といったことも可能です。Goの場合はDIコンテナはないので諦めましょう。*1
- model.go
package main func NewEmployee(employeeType string) Employee { switch employeeType { case "engineer": return Employee{strategy: EngineerStrategy{}} case "salesman": return Employee{strategy: SalesmanStrategy{}} default: return Employee{} } } type Employee struct { strategy EmployeeStrategy } func (e Employee) payAmount() int { if e.strategy == nil { return -1 } return e.strategy.payAmount() } type EmployeeStrategy interface { payAmount() int } type EngineerStrategy struct{} func (es EngineerStrategy) payAmount() int { return 1 } type SalesmanStrategy struct{} func (ss SalesmanStrategy) payAmount() int { return 10 }
- main.go
package main import "fmt" func main() { e := NewEmployee("engineer") fmt.Println("amount", e.payAmount()) }
- 出力結果
$ go run . amount 1
Employee
構造体は呼び出し元から呼び出されますが、アルゴリズムの詳細は Employee
構造体のフィールドに含まれる EmployeeStrategy
インターフェースの payAmount
メソッドに移譲しています。main.go
ではインターフェースのメソッドを呼び出しています。NewEmployee
のコンストラクタの引数によって振る舞いを差し替えることができました。
*1:google/wireは必要な依存関係のコードを生成してくれますが、SpringのDIコンテナのように実行時にインスタンスを取得するものではありません