1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Kotlinでストラテジーパターン

Posted at

はじめに

良いコード/悪いコードで学ぶ設計入門を読んでいて、「ストラテジーパターンってこんなに便利なのか!」と驚いたのでまとめてみます。

0. ストラテジーパターンとは

すごい大雑把に言うと、ifやwhenなどを使わず、interfaceを用いることで、条件分枝処理を実装するような設計です。詳しい説明やクラス図についてはTECHSCOREさんやIT専科さんに書いてありますので、そちらを見てくださいね。

今回は、映画館で料金(大人:1800, 子供:1000, ペア:2500)とプレゼント(大人:プレゼントなし, 子供:ぬいぐるみ, ペア:花束)を返却するような処理を考えてみます。

1. ストラテジーパターンなしで実装するとどうなるか

ストラテジーパターンなしでの実装例です。

Cinema.kt
class Cinema(val type: Type){
    
    fun price(): Int {
        val price = 
            when(type){
                Type.ADULT -> 1800
                Type.KID -> 1000
                Type.PAIR -> 2500
            }
        return price
    }
    
    fun present() {
        when(type){
            Type.ADULT -> println("なし")
            Type.KID -> println("ぬいぐるみ")
            Type.PAIR -> println("花束")
        }
    }
}
Type.kt
enum class Type{
    ADULT,
    KID,
    PAIR
}
main.kt
fun main() {
    val cinema = Cinema(Type.PAIR)
    val price = (cinema.price())
    println(price)
    cinema.present()   
}

この程度の処理でしたら、正直なところストラテジーパターンを使う必要はないかもしれません。
しかし、属性(type)が"シニア", "大人", "大学生", "小中高生", "幼稚園以下", "ペア", "特別パスポート会員" and more...と増え、メソッドもprice(), present(), numberOfSeat() and more...と増えると話は別です。あっという間に下のようなクソコードが出来上がるでしょう。

Cinema.kt
class Cinema(val type: Type){
    
    fun price(): Int {
        val price = 
            when(type){
                Type.SENIOR -> 1500
                Type.ADULT -> 1800
                Type.UNIVERSITY_STUDENT -> 1000
                Type.KID -> 500
                Type.BABY -> 0
                Type.PAIR -> 2500
                Type.SPECIAL -> 1000
        }
        return price
    }
    
    fun present() {
        when(type){
                Type.SENIOR -> println("なし")
                Type.ADULT -> println("なし")
                Type.UNIVERSITY_STUDENT -> println("うまい棒")        
                Type.KID -> println("ぬいぐるみ")
                Type.BABY -> println("ぬいぐるみ")
                Type.PAIR -> println("花束")
                Type.SPECIAL -> println("記念写真")
        }
    }

        fun numberOfSeat(): Int {
        val numberOfSeat = 
            when(type){
                Type.SENIOR -> 1
                Type.ADULT -> 1
                Type.UNIVERSITY_STUDENT -> 1
                Type.KID -> 1
                Type.BABY -> 0
                Type.PAIR -> 2
                Type.SPECIAL -> 1
        }
        return numberOfSeat
    }
}

これでは
①コードの重複が多数あるため、属性が追加された時に実装漏れが起こりやすい
②メソッドが追加されるたびに、重複コードが生じる
など保守性に乏しいコードになってしまいます。ちなみに上のコードだと、presentメソッドでType.SPECIALを実装し忘れたとしてもコンパイルは通ってしまいます。あな恐ろしや恐ろしや。

そこでストラテジーパターンの登場です。

2. ストラテジーパターンを用いて実装するとどうなるか

ストラテジーパターンでは、アルゴリズムをStrategyインターフェイスに記述し、これを属性毎の具体的なStrategyクラスで実装します。

Strategy.kt
interface Strategy{    
    fun price(): Int
    fun present()
}
AdultStrategy.kt
class AdultStrategy: Strategy{
    override fun price(): Int {
        return 1800
    }
    
    override fun present(){
        println("なし")
    }
}
KidStrategy.kt
class KidStrategy: Strategy{
    override fun price(): Int {
        return 1000
    }
    
    override fun present(){
        println("ぬいぐるみ")
    }
}
PairStrategy.kt
class PairStrategy: Strategy{
    override fun price(): Int {
        return 2500
    }
    
    override fun present(){
        println("花束")
    }
}

その後、Strategyを切り替え役をContextクラスで行います。

Context.kt
class Context(val strategy: Strategy){
	fun price(): Int {
        return strategy.price()
    }
    
    fun present() {
        return strategy.present()
    }
}
main.kt
fun main() { 
    val cinema = Cinema(AdultStrategy())
    val price = (cinema.price())
    println(price)
    cinema.present()
}

上ではmain()でAdultStrategyをインスタンス化していますが、以下のようにmapを使えばenumを渡すだけで処理ができます。

main.kt
fun main() {
    val adultStrategy = AdultStrategy()
    val kidStrategy = KidStrategy()
    val pairStrategy = PairStrategy()
    
    val maps = hashMapOf<Type, Strategy>()
    maps.put(Type.ADULT, adultStrategy)
    maps.put(Type.KID, kidStrategy)
    maps.put(Type.PAIR, pairStrategy)
    
    val context = Context(maps.get(Type.ADULT) ?: adultStrategy)
    val price = (context.price())
    println(price)
    context.present()
}

Strategyパターンでは、アルゴリズムをStrategyインターフェイスに記述するということがミソです。
属性が増えた場合、具体的なStrategyクラスを作成してインターフェースを実装すれば良いので、機能追加が簡単になり、コードの修正漏れも大幅に減少します!

参考にさせていただきました
https://zenn.dev/nekoniki/articles/396ebc523930c196dc13

ここ間違っているよーなど、コメントありましたら、ぜひお願いします!

1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?