본문 바로가기
프로그래밍 언어/설계 원칙 & 디자인 패턴

저는 상태 패턴이 처음이라니까요?

by 조엘 2021. 3. 19.

안녕하세요! 조엘입니다!

 

"처음이라니까요" 시리즈 세 번째 주자 바로 상태 패턴입니다. 

 

상태 패턴을 제가 어떻게 이해하고, 정리하고, 적용했는지 알려드릴게요! 💪

피드백 환영입니다! 댓글 달아주세요 :) 

 

 

*** 전략 패턴 복습 ***

앞선 포스팅에서 전략 패턴에 대해서 알아봤어요!

- 전략 패턴 포스팅 : papimon.tistory.com/75

 

전략 패턴은 객체 간의 커뮤니케이션에 유연성을 부여하는 Behavioral 패턴 중에 하나였어요. 

 

전략 패턴은 특정 전략(알고리즘) 어플리케이션의 동작에 필요하며,

사용자의 요청 등 상황에 따라 특정 전략(알고리즘)이 런타임에 바뀌어야 할 때 유용한 디자인 패턴이에요.

 

여러 전략들을 추상화한 interface를 만들었고, 실제 전략들을 구체화시킨 class들을 만들었어요.

이를 활용해 전략 사용자는 코드 변경 없이 다양한 전략을 사용할 수 있게 되었고,

결과적으로 객체 간의 유연성이 증대된 코드 작성이 가능해졌어요. 

 

오늘은 또 다른 Behavioral 패턴인 상태 패턴에 대해 알아보도록 해요!

 

 

*** 상태 패턴 ***

위키피디아에서 정의하는 상태 패턴의 정의는 다음과 같아요. 

 

상태 패턴(state pattern)은 객체 지향 방식으로 상태 기계를 구현하는 행위 소프트웨어 디자인 패턴이다.

상태 패턴을 이용하면 상태 패턴 인터페이스의 파생 클래스로서 각각의 상태를 구현함으로써,

또 패턴의 슈퍼클래스에 의해 정의되는 메소드를 호출하여 상태 변화를 구현함으로써 상태 기계를 구현한다.

 

상태 패턴은 패턴의 인터페이스에 정의된 메서드들의 호출을 통해 현재의 전략을 전환할 수 있는 전략 패턴으로 해석할 수 있다.

 

이번에도 정의가 참 어려운데요 😅

상태 패턴의 구조를 살펴보고, 예시를 살펴보면서 이해해보도록 해요!

 

 

*** 상태 패턴의 구성 요소 ***

상태 패턴을 이루는 구성 요소는 다음과 같아요. 

 

상태 패턴의 구조

 

- 요청자 (client) : 상태 보유자에게 특정 기능 수행을 요청

- 상태 보유자(context) : 특정 기능 수행 요청을 받으면, 상태 콘트리트에게 이를 위임

- 상태(state) : 구체화된 여러 상태들의 추상화로써, 상태가 수행하는 기능들의 모음

    - 상태 콘트리트(concrete state) : 구체적 상태에 따른 알맞은 기능들을 구현

 

보다시피 현재 상태에 따라 다른 기능을 제공해야 하는 경우 상태 패턴을 사용할 수 있어요. 

이런 발상을 코드로 옮기기 위해서 어떻게 상태와 상태 콘크리트를 작성할 수 있을까요?

 

전략 패턴과 마찬가지로, 인터페이스와 해당 인터페이스를 구현한 클래스를 통해 구현해요. 

인터페이스를 통해 각 상태가 제공할 기능을 추상화한 후,

이를 구현한 알맞은 상태 콘크리트를 상태 사용자가 갖도록 하는 것이죠. 

 

 

*** 상태 패턴 예시 ***

자, 이제 코드로 실제 상태들이 어떻게 구현되는지 살펴보아요. 💪💪

 

public static void main(String[] args) {
    final Laptop laptop = new Laptop();
    laptop.pushPower();
    laptop.startCoding();
}

요청자 (client) 에요. 

해당 main 메서드는 Laptop 객체를 생성한 후, pushPower()와 startCoding() 기능 수행을 요청했어요.

 

public class Laptop {
    private LaptopState laptopState;

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

    public void pushPower() {
        laptopState = laptopState.pushPower();
    }

    public void startCoding() {
        laptopState.startCoding();
    }
}

상태 보유자 (context) 에요.

Laptop 클래스는 LaptopState를 인스턴스 변수로 가지고 있어요. 

처음 Laptop 객체를 생성하면, LaptopState는 Off 객체로 초기화됨을 알 수 있어요. 

Laptop 객체가 pushPower()와 startCoding() 기능 수행 요청을 받으면, LaptopState에게 이를 위임해요. 

 

public interface LaptopState {
    LaptopState pushPower();
    void startCoding();
}


public class On implements LaptopState {
    @Override
    public LaptopState pushPower() {
        return new Off();
    }

    @Override
    public void startCoding() {
        System.out.println("재미난 코딩!");
    }
}


public class Off implements LaptopState {
    @Override
    public LaptopState pushPower() {
        return new On();
    }

    @Override
    public void startCoding() {
        System.out.println("먼저 전원을 켜주세요!");
    }
}

상태 (state)상태 콘크리트 (state concrete) 에요. 

위에서 언급했던 것처럼, LaptopState라는 인터페이스를 만들어 Laptop이 제공해야 할 기능을 추상화 시켰어요. 

이제 해당 인터페이스를 구현한 상태 콘크리트 클래스는 각각 Laptop 상태에 알맞은 기능을 구현해요. 

 

On 상태 콘크리트를 같이 볼까요?

On 상태 콘크리트는 pushPower() 라는 메서드를 통해 Off 객체를 생성하여 반환해요. 

이를 호출한 Laptop 객체는 이제 Off라는 상태를 가지게 되겠네요. 

또한, startCoding() 이라는 메서드는 "재미난 코딩!"을 출력해줘요. 

 

Off 상태 콘크리트는 어떤가요? 

Off 상태 콘크리트는 pushPower() 라는 메서드를 통해 On 객체를 생성하여 반환해요.

이를 호출한 Laptop 객체는 이제 On 이라는 상태를 가지게 되겠네요. 

또한, startCoding() 이라는 메서드는 "먼저 전원을 켜주세요!"를 출력해주네요!

 

이로써 우린 Laptop의 현재 상태에 알맞게 요청에 대한 처리를 해줄 수 있게 되었어요.

또한 자연스럽게 Laptop의 상태를 업데이트 할 수 있었고요. 

 

 

*** 상태 패턴의 장단점 ***

상태 패턴은 이와 같이 객체 간의 커뮤니케이션에 유연성을 부여했어요. 

 

상태 패턴을 적용하지 않았더라면, Laptop 객체 안에서 모든 처리를 해줬을 것이에요. 

현재 상태가 On 인지, Off 인지를 검증하고, 그에 알맞는 처리들을 주렁주렁 달아줬을 것이에요. 

 

이제 상태패턴의 장단점을 정리해 봅시다. 

 

[장점]

  - 관련된 코드가 한 곳에 모여있어 안전하고 빠른 구현이 가능하다.

      - 각각의 상태에 대해 유지보수가 용이하다.

      - 상태마다 달라지는 validation 등이 쉬워진다.

 

  - 상태가 많아지더라도 코드의 복잡도가 증가하지 않는다. 

      - 이를 통해 if - else 분기를 제거할 수 있다. 

      - if - else 분기 제거를 통해, 단일 책임 원칙을 준수하기 더 수월해진다. 

 

  - 유한 상태 머신을 상태 패턴으로 구현한다면, 굉장히 효율적이다. 

 

[단점]

  - 상태 보유자가 가질 수 있는 모든 상태에 대해 알아야 한다. 

      - 상태의 구조가 한 눈에 파악이 어렵다면, 오히려 유지보수가 어려워질 수도 있다. 

 

  - 작성해야 할 코드의 양이 많다. 

      - 추상화 된 모든 메서드를 구현해 줘야 하기 때문에 코드의 양이 많아진다. 

 

 

*** 전략 패턴 vs 상태 패턴 ***

지금까지 전략 패턴과 상태 패턴, 두 개의 디자인 패턴을 공부해봤는데요. 

이 둘은 구조상 유사한 점이 많아요. 하지만 둘이 사용되어야 할 상황은 분명히 다르답니다. 

 

두 디자인 패턴의 차이점을 언급한 stackoverflow 답변을 번역하며 포스팅 마칠게요!

 

- 상태 패턴에서 상태는 스스로를 다른 상태로 변경할 수 있지만, 전략 패턴에서 이는 불가능하다. 

- 상태 패턴에서 상태는 상태 보유자가 직접 가지고 있지만, 전략 패턴에서 전략은 매개변수로 주입된다.

- 상태 패턴에서 상태는 상태 보유자의 거의 모든 행동을 수행하지만, 전략 패턴에서 전략은 하나의 특정한 임무를 수행한다.

 

 

참고

- stackoverflow.com/questions/1658192/what-is-the-difference-between-strategy-design-pattern-and-state-design-pattern

- 개발자가 반드시 정복해야 할 객체 지향과 디자인 패턴(최범균 | 인투북스)

 

 

 

반응형

댓글