BuNa_
IT Story
BuNa_
전체 방문자
오늘
어제
  • 분류 전체보기 (117)
    • CS (14)
      • 운영체제 (8)
      • 네트워크 (0)
      • Design Pattern (1)
      • OOP (4)
    • 대외활동 (24)
      • 우아한테크코스 (14)
      • DND 동아리 (4)
      • UMC 동아리 (5)
      • 해커톤 (1)
    • Android (29)
      • MVVM (2)
      • 스터디 (11)
      • Compose (3)
      • Unit Test (1)
    • Project (5)
      • 어따세워 (5)
      • DnD 과외 서비스 (0)
    • Programming (11)
      • Kotlin (4)
      • 파이썬 (7)
    • Git (1)
    • 인공지능 (22)
    • 백준 (8)
    • 기타 (3)
      • IntelliJ (1)
      • 일상 (0)

블로그 메뉴

  • 홈

공지사항

인기 글

태그

  • 파이썬
  • 안드로이드
  • 선형회귀
  • 컴공선배
  • RecyclerView
  • Compose
  • 우테코 프리코스
  • Ai
  • 원시값 포장
  • ViewModel
  • 우아한테크코스
  • External fragmentation
  • 셀레니움
  • 백준
  • 딥러닝
  • MVVM
  • 다이나믹 프로그래밍
  • 외부 단편화
  • 인공지능
  • 어따세워
  • 우테코
  • 우테코 5기
  • 객체지향 생활체조
  • UMC
  • K-means
  • Android
  • k-means++
  • 운영체제
  • Baekjoon
  • 인공지능 분류

최근 댓글

최근 글

티스토리

hELLO · Designed By 정상우.
BuNa_

IT Story

[Design Pattern] 상태 패턴(State Pattern)
CS/Design Pattern

[Design Pattern] 상태 패턴(State Pattern)

2023. 3. 13. 21:57

 

State 패턴

하나의 객체가 다양한 상태를 가질 때가 있습니다.
또한, 상태가 변화함에 따라 행위가 달라질 수 있습니다.
만약 어떤 상태에 대한 `행위를 클래스별로 구현`해주어야 한다면 상당한 `보일러플레이트 코드`가 발생할 것입니다.
이는 곧 프로젝트의 유지보수성을 떨어뜨리는 것과 동일합니다.

이러한 상황을 쉽게 관리하기 위해 State 패턴을 사용할 수 있습니다.
State 패턴은 `상태에 따른 비즈니스 로직`, `상태 전이`를 상태 클래스 내에서 관리합니다.
이러한 상태를 가지고 있는 객체를 `Context`라고 부릅니다.
State 패턴을 적용하면 Context는 각 상태에 따라 어떤 기능을 수행할지 고려하지 않아도 됩니다.

 


State 패턴을 적용하지 않은 사례

// 자동차는 아래와 같은 상태를 지닌다.
// 1. 고장 (고장난 상태)
// 2. 노말 (고장나지 않은 상태)
// 3. 파워 (파워 모드 상태)
data class Car(
    val position: Int,
    val state: String,
) {
    fun move(): Car {
        when(state) {
            "고장" -> return copy() // 움직이지 못한다.
            "노말" -> return copy(position = position + 1) // 한 칸 이동한다.
            "파워" -> return copy(position = position + 2) // 두 칸 이동한다.
        }
        throw IllegalStateException("올바른 상태가 아닙니다.")
    }

    fun drift(): Car {
        when(state) {
            "고장" -> return copy() // 움직이지 못한다.
            "노말" -> return copy(position = position + 5) // 정상 모드에서 드리프트하면 5칸 이동할 수 있다.
            "파워" -> return copy(state = "고장") // 파워 모드에서 드리프트하면 고장난다.
        }
        throw IllegalStateException("올바른 상태가 아닙니다.")
    }

    fun moveBack(): Car {
        when(state) {
            "고장" -> return copy() // 움직이지 못한다.
            "노말" -> return copy(position = position - 1) // 뒤로 한 칸 이동한다.
            "파워" -> return copy(position = position - 2) // 뒤로 두 칸 이동한다.
        }
        throw IllegalStateException("올바른 상태가 아닙니다.")
    }
}


위 코드는 상태 패턴을 적용하지 않은 상황입니다.
자동차는 `고장` `노말 모드` `파워 모드` 를 가지고 있습니다.
자동차의 멤버 메서드를 호출하면, 조건문을 통해 각 상태에 따른 기능을 수행합니다.

위 코드에는 문제점이 있습니다.
자동차의 로직이 추가됨에 따라 매번 자동차의 상태를 조건문을 통해 확인해주어야 한다는 것입니다.
이는 반복적이고 비슷한 형태의 코드를 초례합니다.
또한, 상태가 추가되었을 때 자칫 조건문을 하나라도 추가해주지 않으면 Exception이 발생하여 예상치 못한 결과가 발생합니다.


상태 코드를 적용한다면?

interface CarState {
    fun move(car: Car): Car
    fun drift(car: Car): Car
    fun moveBack(car: Car): Car
}

 

문제를 파악했으니, State 패턴을 적용하여 위 문제를 해결해보겠습니다.
자동차의 상태에 따라 위의 3가지 행위를 수행할 수 있습니다.
CarState 인터페이스를 구현하는 상태 클래스를 구현해보겠습니다.


고장난 상태

import Car
import CarState

class Broken : CarState {
    // 움직이지 못한다.
    override fun move(car: Car): Car = car.copy()

    // 드리프트 하지 못한다.
    override fun drift(car: Car): Car = car.copy()

    // 후진하지 못한다
    override fun moveBack(car: Car): Car = car.copy()
}


노말 모드 상태

import Car
import CarState

class Normal : CarState {
    override fun move(car: Car): Car =
        car.copy(position = car.position + 1)

    override fun drift(car: Car): Car =
        car.copy(position = car.position + 5)

    override fun moveBack(car: Car): Car =
        car.copy(position = car.position - 1)
}


파워 모드 상태

import Car
import CarState

class Power : CarState {
    override fun move(car: Car): Car =
        car.copy(position = car.position + 2)

    override fun drift(car: Car): Car =
        car.copy(state = Broken())

    override fun moveBack(car: Car): Car =
        car.copy(position = car.position - 2)
}


자동차 클래스

data class Car(
    val position: Int,
    val state: CarState,
) {
    fun move(): Car = state.move(this)

    fun drift(): Car = state.drift(this)

    fun moveBack(): Car = state.moveBack(this)
}



이제 각 상태에 따라 자동차 클래스에서 분기처리해줄 필요가 없어졌습니다.
if 문이 없어지면서 가독성이 높아졌고, 상태가 추가되면 기존 코드를 수정하지 않고도 새로운 상태 클래스를 확장해주기만 하면 됩니다.
즉, OCP를 지키면서 확장성과 유지보수성이 높아지는 장점을 가지게 되었습니다.

저작자표시 비영리 변경금지 (새창열림)
    BuNa_
    BuNa_
    안드로이드 개발자를 향해 달리고 있는 공대생입니다! 🧑 Android, Kotlin, Java, Python 등 학습하고 있는 내용과 프로젝트를 주로 업로드하고 있습니다. 지적과 조언은 언제나 환영입니다!😊 github : https://github.com/tmdgh1592

    티스토리툴바