🖐 들어가기 전에
상태(현재 진행중인 행위)를 나타내기 위해 어떤 방식을 사용했을까?
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/
이 글이 쉽고 예시도 좋아서 이해하기 쉬웠고 상태 패턴의 필요성을 느끼기에도 적절했다!
시간 있으면 이 글도 읽어보길 바란다!
참고)
https://victorydntmd.tistory.com/294
https://coding-start.tistory.com/247
https://always-intern.tistory.com/9
'야미스터디 > Design pattern' 카테고리의 다른 글
[디자인 패턴] 프록시 패턴 📌 (0) | 2022.10.05 |
---|---|
[디자인 패턴] 퍼사드 패턴 📌 (0) | 2022.10.04 |
[디자인 패턴] 싱글톤 패턴 📌 (0) | 2022.08.21 |
[디자인 패턴] 디자인 패턴이란? (0) | 2022.08.21 |
댓글