본문 바로가기

웹개발 - Back 관련/Architecture

[ GOF ] 상태 패턴 - State Pattern with Kotlin

Topic =  상태 패턴의 개념에 대해서 학습해보고 Kotlin에서 적용 예시를 만들어보기.

 

🔑 Purpose

  • 객체의 상태를 캡슐화한 클래스들로 분리하여, 복잡한 조건문을 사용하지 않고, 위임을 통해 객체의 상태에 따라 다른 행동을 할 수 있도록 한다.

 


 

Why 

 

 

위 예시 사진 이미지와 같이 TodoList의 각각의 할 일들의 개별 속성 ( 마감기한, 선택목록, 체크박스, 텍스트, 백분율 등 ) 의 표기는 해당 객체의 속성이 날짜면 날짜 데이터만을 표기하고, 백분율은 숫자만을 표기하며 %로 표기하는 등 각각의 상태에 따라 다른 표기 방식을 갖게 된다.

 

위 상황을 상태 패턴을 적용하지 않은 코드로 일부분을 작성해보면 다음과 같이 작성할 수 있다.

import io.github.oshai.kotlinlogging.KotlinLogging

private val logger = KotlinLogging.logger {  }
class Task(private var categoryType: String = CATEGORY_TEXT) {

    companion object {
        const val CATEGORY_TEXT = "text"
        const val CATEGORY_DATE = "date"
        const val CATEGORY_NUMBER = "number"
    }

    fun setCategoryType(categoryType: String) {
        this.categoryType = categoryType
    }

    fun write(content: String) {

        when(categoryType) {
            CATEGORY_TEXT -> {
                logger.info { "TEXT = $content" }
            }
            CATEGORY_DATE -> {
                // 날짜 선택 로직
                logger.info { "DATE = 날짜 " }
            }
            CATEGORY_NUMBER -> {
                val result = content.replace(Regex("[^0-9]"), "")
                logger.info { "NUMBER = $result" }
            }
        }
    }
}

 

카테고리 종류가 10개가 넘어가기만 해도 when 조건문 안에 10개 이상의 조건이 생기게 되는 것이다. 때문에 조건문 코드가 비대해지는 문제가 발생하고, 객체 내부에서 상태 관리와 비즈니스 로직 코드가 함께 있으므로 SRP 를 어기게 되는 문제도 발생한다.

 

이를 해결하기 위해 상태의 행동에 관련된 로직을 캡슐화하여 객체에서 분리해서 사용하는 것이 바로 상태 패턴이다.

 

위 예제를 상태 패턴의 구조를 학습하면서 상태 패턴이 적용된 코드로 바꾸어 보자.

 

 

 

 

상태 패턴의 구조

 

[ 상태 패턴 - UML 다이어그램 ]

+------------------+        +----------------+
|      Context     |        |    <<State>>   |
+------------------+        +----------------+
| - state: State   |<>----->| +handle():Unit |
+------------------+        +----------------+
| +setState(State) |        |                |
| +request()       |        |                |
+------------------+        +----------------+
                                    /\
                                    ||
              +---------------------+---------------------+
              |                                           |
+-------------------------+              +-------------------------+
|  ConcreteStateA         |              |  ConcreteStateB          |
+-------------------------+              +-------------------------+
| +handle():Unit          |              | +handle():Unit           |
+-------------------------+              +-------------------------+

 

 

[ Context ]

  • 현재 상태를 추적하여 ( 현재 상태를 유지하고, 상태 변경 시 자신의 상태를 업데이트하는 것 ) 상태에 따라 행동 동작을 위임하는 객체이다.
class StatePatternTask(private var categoryType: Category = Text()) {

    fun setCategoryType(categoryType: Category) {
        this.categoryType = categoryType
    }

    fun write(content: String) {
        categoryType.write(content)
    }
}

 

 

[ State Interface ]

  • 상태의 행동을 정의하는 인터페이스이다.
interface Category {
    fun write(content: String)
}

 

 

[ Concrete State ]

  • 상태 인터페이스를 구현하는 구체적인 상태 클래스로, 각각의 상태의 행동을 정의한다.
import io.github.oshai.kotlinlogging.KotlinLogging
private val logger = KotlinLogging.logger {  }
class Date : Category {

    override fun write(content: String) {
        // 날짜 선택 로직
        logger.info { "DATE = 날짜 " }
    }
}

class Number : Category{

    override fun write(content: String) {
        val result = content.replace(Regex("[^0-9]"), "")
        logger.info { "Number.write = $result" }
    }
}

class Text: Category {
    override fun write(content: String) {
        logger.info { "Text = $content" }
    }
}

 

상태 패턴 장점, 단점 정리

[ 상태 패턴의 장점 ]

  • 복잡한 조건문 제거
    • 객체의 상태에 따라 행동을 다르게 해야 하는 경우 if, when 구문을 사용할 필요 없이, 각 상태를 객체로 분리하여 조건문 없이 상태에 따른 로직을 구현할 수 있다.
  • 유연한 상태 변경 - SRP, OCP
    • task객체 내부에서 상태에 관한 로직을 관리하지 않고, 상태 클래스들을 독립적으로 관리하므로 새로운 상태가 추가되거나, 기존 상태가 변경되어도 task 객체를 건드리지 않고도 기능 확장, 수정이 가능하다.

 

 

[ 상태 패턴의 단점 ]

  • 복잡도 증가
    • 상태 클래스가 많아질수록 클래스 수가 증가하므로 복잡도가 높아질 수도 있다.
    • ➡️상태 클래스가 별도의 상태를 갖지 않아 클래스 생성이 필요하지 않고, 상태가 정형화 되어 있고, 상태 전이가 복잡하지 않은 경우에 Enum class를 사용하여 상태를 표현하여 복잡도를 낮추는 방법도 있다.

 

 

 

 

 


 

[ 다음에 학습할 내용 ]

  • UML 다이어그램 정리
  • ENUM 

 

 

 


[Reference]

 

상태 패턴

/ 디자인 패턴들 / 행동 패턴 상태 패턴 다음 이름으로도 불립니다: State 의도 상태 패턴은 객체의 내부 상태가 변경될 때 해당 객체가 그의 행동을 변경할 수 있도록 하는 행동 디자인 패턴입니

refactoring.guru