技術メモ

技術メモ

ラフなメモ

条件付きのロジックを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 が追加になった場合には、呼び出し元の実装には変更は不要です。変更の範囲を局所化できます。今回の場合は employeeTypeengineersalesman の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コンテナのように実行時にインスタンスを取得するものではありません