본문 바로가기
야미스터디/Design pattern

[디자인 패턴] 상태 패턴 (State Pattern) 📌

by 의정부핵꿀밤 2022. 8. 24.
728x90

🖐 들어가기 전에

상태(현재 진행중인 행위)를 나타내기 위해 어떤 방식을 사용했을까?

 

Enum (열거형)

  • Enum이란 서로 연관된 상수들의 집합니다
  • 상태를 나타내기에는 편리한 방식이기 때문에 많이 사용된다
  • 그러나 Enum은 객체 지향적인 프로그래밍이라고 하기에는 애매한 부분이 있다😥

 

Enum의 단점

  • enum 하나로 모든 상태를 관리하는 것은 편리하나, 메서드를 실행하기 위해서 많은 분기가 생긴다
  • 만약 Enum 값을 기준으로 분기문을 사용할 때, 상태가 계속해서 추가되거나 기능의 수정이 발생하는 경우에는 OCP원칙을 준수하지 못한다
  • 상태가 많아질수록 복잡해지는 조건문이 여러 코드에서 중복해서 출현하고 그만큼 코드 변경을 어렵게 만든다!

 

 


상태 패턴 (State Pattern)

  • 객체 자신의 내부 상태에 따라 행위를 변경하도록 하는 패턴
    • 여기서 상태란 객체가 가질 수 있는 어떤 조건이나 상황을 의미힌다
    • 객체의 특정 상태 - 클래스
    • 상태에 따른 행위 - 클래스 내 메서드
    • 상태 클래스를 인터페이스로 캡슐화
  • 객체가 특정 상태에 따라 행위를 달리하는 상황에서, 자신(객체)이 직접 상태를 체크하여 상태에 따라 행위를 호출하지 않고 상태를 객체화하여 상태가 행동을 할 수 있도록 책임을 위임하는 패턴
    • 즉, 객체의 특정 상태를 클래스로 선언하고, 클래스에서는 해당 상태에서 할 수 있는 행위들을 메서드로 정의한다
    • 그리고 위와 같은 상태 클래스를 인터페이스로 캡슐화하여 클라이언트에서 인터페이스로 호출하는 방식으로 사용한다
  • 즉, 같은 상태에 대한 모든 동작과 데이터를 클래스 하나에 캡슐화하는 패턴이다

 

  • State
    • 시스템의 모든 상태에 공통의 인터페이스를 제공한다
    • 따라서 이 인터페이스를 실체화한 어떤 상태 클래스도 기존 상태 클래스를 대신해 교체해서 사용할 수 있다
  • State1, State2, State3
    • Context 객체가 요청한 작업을 자신의 방식으로 실제로 실행한다
    • 대부분의 경우 다음 상태를 결정해 상태 변경을 Context 객체에 요청하는 역할도 수행한다
  • Context
    • State를 이용하는 역할을 수행한다
    • 현재 시스템의 상태를 나타내는 상태 변수(State)와 실제 시스템의 상태를 구성하는 여러가지 변수가 있다
    • 또한 각 상태 클래스에서 상태 변경을 요청해 상태를 바꿀 수 있도록 하는 메서드(setState)가 제공된다
    • Context 요소를 구현한 클래스의 request 메서드는 실제 행위를 실행하는 대신 해당 상태 객체에 행위 실행을 위임한다

 

 

 

상태 패턴을 사용하는 상황

  • 객체의 행동이 상태에 따라 달라질 수 있고, 객체의 상태에 따라 런타임에 행동이 바뀌어야 하는 경우
  • 객체의 상태에 따라 달라지는 다중 분기 조건 처리가 많이 있는 경우
  • 객체의 행동이 상태에 영향을 받으며, 상태 간 전환이 명시적인 경우
  • 현재 상태에 따라 다르게 동작하는 객체가 있고 상태의 수가 많이 있고 상태별 코드가 자주 변경되는 경우
  • 클래스 필드의 현재 값에 따라 클래스가 동작하는 방식을 변경하는 대규모 조건으로 클래스가 오염된 경우
  • 조건 기반 상태 머신의 유사한 상태 및 전환에 걸쳐 중복 코드가 많은 경우

 

 

 

상태 패턴의 장점

  • 상태에 따른 행동을 국소화하며 서로 다른 상태에 대한 행동을 별도의 객체로 관리한다
  • 새로운 상태가 추가되더라도 context 코드가 받는 영향이 적다
  • 하나의 객체에 대한 여러 동작을 구현해야할 때 상태 객체만 수정하므로 동작의 추가, 삭제 및 수정이 간단해진다
  • State 패턴을 사용하면 객체의 상태에 따른 조건문(if/else, switch)이 줄어들어 코드가 간결해지고 가독성이 올라간다
  • 상태에 대한 모든 행동 양식이 한 곳에 있기 때문에 유지보수가 쉽다

 

 

 

상태 패턴의 단점

  • 상태에 따른 조건문을 대신한 상태 객체가 증가하여 관리해야 할 클래스의 수가 증가하여 오히려 유지보수가 힘들어질 수도 있다
  • 상태에 따라 변하는 메서드의 숫자가 적다면 오히려 불필요한 복잡성을 추가할 수 있다

 

 

 

 

상태 패턴 예시

  • 게시물의 좋아요/싫어요 버튼
  • 형광들 불 ON/OFF

 

[실제 예시와 코드]

예를 들어 노트북을 켜고 끄는 상황이라고 생각해보자

 

노트북의 동작

  • 노트북 전원이 켜져 있는 상태에서 전원 버튼을 누르면, 전원을 끌 수 있다
  • 노트북 전원이 꺼져 있는 상태에서 전원 버튼을 누르면, 전원을 켤 수 있다

 

위와 같은 행위를 할 수 있는 Laptop 클래스는 다음과 같이 정의할 수 있다

public class Laptop {
    public static String ON = "on";
    public static String OFF = "off";
    private String powerState = "";

    public Laptop(){
        setPowerState(Laptop.OFF);
    }

    public void setPowerState(String powerState){
        this.powerState = powerState;
    }

    public void powerPush(){
        if ("on".equals(this.powerState)) {
            System.out.println("전원 off");
        }
        else {
            System.out.println("전원 on");
        }
    }
}

 

아래는 Laptop 클래스를 사용하는 Client 클래스의 main 함수이다

public class Client {
    public static void main(String args[]){
        Laptop laptop = new Laptop();
        laptop.powerPush();
        laptop.setPowerState(Laptop.ON);
        laptop.powerPush();
    }
}
  • 노트북이 on 상태이면 노트북 상태를 off로 변경하고, 상태가 off이면 on으로 변경하는 간단한 코드이다

 

 

그런데 간단하게 켜고 끄는 노트북에 절전모드를 추가한다고 해보자

이 때 상태에 따른 동작은 다음과 같다고 가정하자

  • 노트북 전원이 켜져 있는 상태에서 전원 버튼을 누르면, 전원을 끌 수 있다
  • 노트북 전원이 꺼져 있는 상태에서 전원 버튼을 누르면, 절전 모드가 된다
  • 노트북 절전 모드 상태에서 전원 버튼을 누르면, 전원을 켤 수 있다

 

이렇게 절전 모드가 추가된 Laptop 클래스는 다음과 같이 조건문이 하나 더 추가된다

public class Laptop {
    public static String ON = "on";
    public static String OFF = "off";
    public static String SAVING = "saving";
    private String powerState = "";

    public Laptop(){
        setPowerState(Laptop.OFF);
    }

    public void setPowerState(String powerState){
        this.powerState = powerState;
    }

    public void powerPush(){
        if ("on".equals(this.powerState)) {
            System.out.println("전원 off");
        }
        else if ("saving".equals(this.powerState)){
            System.out.println("전원 on");
        }
        else {
            System.out.println("절전 모드");
        }
    }
}
public class Client {
    public static void main(String args[]){
        Laptop laptop = new Laptop();
        laptop.powerPush();
        laptop.setPowerState(Laptop.ON);
        laptop.powerPush();
        laptop.setPowerState(Laptop.SAVING);
        laptop.powerPush();
        laptop.setPowerState(Laptop.OFF);
        laptop.powerPush();
        laptop.setPowerState(Laptop.ON);
        laptop.powerPush();
    }
}

결과 화면

  • 조건문이 하나 추가된다고 해서 크게 불편한 것은 없는데 뭐가 문제일까?
  • 상태가 여러 개 있다면 분기하는 코드는 굉장히 길어질 것이기 때문에 상태에 따라 하고자 하는 행위를 파악하기가 쉽지 않을 것이다
  • 또한 Laptop의 powerPush() 메서드 뿐만 아니라, 예를 들어 TV의 powerPush(), SmartPhone의 powerPush() 에서 같은 분기문이 사용된다면, 기능이 변경될 때마다 일일이 다 찾아가서 수정을 해야 한다

  • 따라서 이렇게 상태에 따라 행위를 달리해야 하는 경우에 사용하는 패턴이 상태 패턴(State Pattern)이다

 

 

그럼 이제 상태 패턴을 적용해보자!

  • State Pattern을 적용하면 각 상태들, 즉 On/Off/Saving 상태를 클래스로 정의하고, 이들을 하나의 인터페이스로 묶는다
  • 그리고나서 Laptop이 상태 인터페이스의 메서드를 호출하면 각 상태 클래스에서 정의된 행위가 수행되는 방식이다

 

 

위를 코드로 표현하면 다음과 같다

 

1) 먼저 전원 상태를 캡슐화한 인터페이스를 선언한다

public interface PowerState {
    public void powerPush();
}

 

2) 다음으로 PowerState 인터페이스를 구현한 각 상태 클래스를 정의한다

public class On implements PowerState{
    public void powerPush(){
        System.out.println("전원 off");
    }
}
public class Off implements PowerState {
    public void powerPush(){
        System.out.println("절전 모드");
    }
}
public class Saving implements PowerState {
    public void powerPush(){
        System.out.println("전원 on");
    }
}

 

3) 이어서 Laptop 클래스를 수정한다

public class Laptop {
    private PowerState powerState;

    public Laptop(){
        this.powerState = new Off();
    }

    public void setPowerState(PowerState powerState){
        this.powerState = powerState;
    }

    public void powerPush(){
        powerState.powerPush();
    }
}

Laptop 클래스는 이제 상태를 분기하는 코드가 사라지고, 인터페이스의 powerPush() 메서드를 호출하기만 한다

 

4) 마지막으로 Laptop 객체를 사용하는 Client 클래스를 정의한다

public class Client {
    public static void main(String args[]){
        Laptop laptop = new Laptop();
        On on = new On();
        Off off = new Off();
        Saving saving = new Saving();

        laptop.powerPush();
        laptop.setPowerState(on);
        laptop.powerPush();
        laptop.setPowerState(saving);
        laptop.powerPush();
        laptop.setPowerState(off);
        laptop.powerPush();
        laptop.setPowerState(on);
        laptop.powerPush();
    }
}

결과는 이전과 동일하다

 

 

 

 

 

🚨 전략 패턴(Strategy Pattern) vs 상태 패턴(State Pattern)

  • 전략 패턴 : 사용자가 쉽게 알고리즘 전략을 바꿀 수 있도록 유연성을 제공하고, 상속의 한계를 해결하기 위해 나온 패턴
  • 상태 패턴 : 한 객체가 동일한 동작을 상태에 따라 다르게 수행해야 할 경우 사용하는 패턴
  • 전략 패턴을 사용하는 경우, 클라이언트가 구체적인 알고리즘의 수행까지는 아니더라도 어느정도는 구조를 알고 있어야 한다
  • 그러나 상태 패턴의 경우 각 상태 구현 클래스들이 알아서 동작을 수행하기 때문에 클라이언트가 상태를 몰라도 사용이 가능하다

 

 

 


✨ 참고하면 좋을 블로그!

https://tecoble.techcourse.co.kr/post/2021-04-26-state-pattern/

 

상태 패턴(State Pattern)을 사용해보자

🥰 😁 😐 😩 🤬 😴 상태(현재 진행중인 행위)를 나타내기 위해 어떤 방식을 사용해 왔는가? Enum 열거형(Enum) 이란 서로 연관된 상수들의 집합이다. 상태를 나타내기에는 정말 편리한 방식이라

tecoble.techcourse.co.kr

이 글이 쉽고 예시도 좋아서 이해하기 쉬웠고 상태 패턴의 필요성을 느끼기에도 적절했다!

시간 있으면 이 글도 읽어보길 바란다!

 


참고)

https://victorydntmd.tistory.com/294

 

[디자인패턴] 스테이트 패턴 ( State Pattern )

스테이트 패턴 ( State Pattern ) 스테이트 패턴은 객체가 특정 상태에 따라 행위를 달리하는 상황에서, 자신이 직접 상태를 체크하여 상태에 따라 행위를 호출하지 않고, 상태를 객체화 하여 상태

victorydntmd.tistory.com

https://coding-start.tistory.com/247

 

디자인패턴 - 스테이트 패턴(State Pattern),상태 패턴

state의 의미는 '상태'이다. 엘리베이터의 정지, 하강, 상승 상태처럼 객체 상태도 상황에 따라 달라진다. state 패턴은 동일한 동작을 객체 상태에 따라 다르게 처리해야 할 때 사용한다. 이렇게

coding-start.tistory.com

https://always-intern.tistory.com/9

 

[디자인 패턴] 상태 패턴(State Pattern)

1) 개요 상태 디자인 패턴은 객체 내부의 상태에 따라 동작을 변경해야할 때 사용하는 디자인 패턴입니다. - 장점 1. 하나의 객체에 대한 여러 동작을 구현해야할 때 상태 객체만 수정하므로 동작

always-intern.tistory.com

https://mypark.tistory.com/entry/Design-Pattern-%EC%83%81%ED%83%9C-%ED%8C%A8%ED%84%B4State-Pattern%EC%97%90-%EB%8C%80%ED%95%B4-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90

 

[Design Pattern] 상태 패턴(State Pattern)에 대해 알아보자

Definition 상태 패턴(State Pattern)을 사용하면 객체의 내부 상태가 바뀜에 따라 객체의 행동을 바꿀 수 있다. 마치 객체의 클래스가 바뀌는 것과 같은 결과를 얻을 수 있다. 실제로 다른 클래스로 변

mypark.tistory.com

 

728x90

댓글