Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
Tags
- 자취준비
- 막대기자르기
- 회사폐업
- HeadFirstDesignPatterns
- 후니의쉽게쓴시스코라우팅
- 자료구조
- 실업인정인터넷신청
- array
- 생애첫계약
- 정보처리기사개정
- 코딩테스트
- 전화영어
- leetcode
- 후니의쉽게쓴시스코네트워킹
- 순열
- 캡쳐링
- C++
- 사회초년생
- 정보
- 부분합알고리즘
- 취업사실신고
- 프로그래머스
- 알고리즘
- IT기초
- 네트워크
- 동적계획법
- 모여봐요동물의숲
- 실업급여
- 튜터링
- 청년내일채움공제
Archives
- Today
- Total
따봉도치야 고마워
Head First Design Patterns : (10)스테이트 패턴 본문
스테이트 패턴
- 객체의 내부 상태가 바뀜에 따라서 객체의 행동을 바꿀 수 있음
- 마치 객체의 클래스가 바뀌는 것과 같은 결과
Q. 스테이트 패턴과 스트래티지 패턴의 다이어그램이 같은 것 같은데?
- 다이어그램은 같지만 용도의 차이
스테이트 패턴 | 스트래티지 패턴 |
- 상황에 따라 Context에서 여러 상태 객체 중 하나에 행동 위임 - 내부 상태가 변경됨에 따라 컨텍스트의 행동도 자연스럽게 바뀜. - 즉 클라이언트는 상태 객체에 대해 몰라도 됨 - 수많은 조건문 대신에 사용 |
- 일반적으로 클라이언트에서 컨텍스트한테 전략 객체를 결정해줌 - 실행 시 전략 객체를 변경할 수 있는 유연성 제공을 위해 사용 - 상속보다 구성을 이용한 유연성 극대화 |
Q. 반드시 구상 상태 클래스에서 다음 상태가 변경되어야 하는 건지?
- 아님. 상태 전환이 고정되어 있으면 Context 자체에서 변환해도 됨, 동적으로 결정되는 경우 상태 클래스에서 처리 권장
- 상태 전환 코드를 상태 클래스에 넣으면 상태 클래스 간 의존성이 생긴다는 단점이 있음
- 상태 전환 코드를 어느 쪽에 넣을지에 따라 어떤 클래스가 변경에 대해 닫혀있게 되는지도 결정됨.
Q. 여러 Context에서 상태 객체를 공유할 수 있는지?
- 각 상태 객체 내에서 자체 상태를 보관하지만 않으면 가능함
- 상태를 공유할 땐 일반적으로 정적 인스턴스 변수에 할당하는 방법을 사용
Q. 스테이트 패턴을 사용하면 클래스의 개수가 너무 많이 늘어나지 않는지?
- 맞지만 유연성 향상을 위한 비용이라고 생각
- 실제 클래스 개수보다 클라이언트에게 노출되는 클래스 개수가 중요
왕뽑기 예제
- 뽑기 기계의 4가지 상태 (동전 없음, 동전 있음, 알맹이 판매, 알맹이 매진)에 맞춰 적절한 행동을 하는 코드 작성
- if(state == SOLD_OUT) 이런 식의 조건문으로 일일이 검사해야 함
- 만약 새로운 상태가 추가된다면? 유지보수가 점점 어려워짐
- 바뀌는 부분이 캡슐화되지 않음, OCP 원칙을 지키지 못함 등등 여러 문제가 있음
- 상태 객체(State Object)를 만들어 작업을 넘기는 건 어떨까
1) State 인터페이스 및 클래스 정의
- 뽑기 기계와 관련된 모든 행동이 정의된 State 인터페이스와 각 상태 클래스
- 각 상태 클래스는 상태에 맞게 구현
NO_QUARTER | HAS_QUARTER | SOLD | SOLD_OUT | |
insertQuarter() | HAS_QUARTER로 상태 변환 |
동전이 이미 넣어져있다는 메시지 출력 | 판매중이니 기다려달라는 메시지 출력 | 매진 메시지 출력 |
ejectQuarter() | 동전을 넣어달라는 메시지 출력 |
NO_QUARTER로 상태 변환 |
판매중이라 반환 불가 메시지 출력 |
매진 메시지 출력 |
turnCrank() | 동전을 넣어달라는 메시지 출력 |
SOLD로 상태 변환 |
한 번만 돌려달라는 메시지 출력 |
매진 메시지 출력 |
dispense() | 동전을 넣어달라는 메시지 출력 |
알맹이가 나가지 않았다는 메시지 출력 | 알맹이 하나 내보냄 남은 개수 > 0이면NO_QUARTER 0이면 SOLD_OUT |
매진 메시지 출력 |
2) 기존 조건문 코드를 전부 삭제하고, 상태 클래스에 작업 위임
//기존 코드
public class GumballMachine{
final static int SOLD_OUT = 0;
final static int NO_QUARTER = 1;
final static int HAS_QUARTER = 2;
final static int SOLD = 3;
int state = SOLD_OUT;
int count = 0;
..
public void insertQuarter(){
if(state == HAS_QUARTER){
..
}else if(state == NO_QUARTER){
..
}else if(state == SOLD_OUT){
..
}else if(state == SOLD){
..
}
}
..
}
================================>
//변경된 코드
public class GumballMachine{
//상수 대신 상태 클래스를 사용
State soldOutState;
State noQuarterState;
State hasQuarterState
State soldState;
State state = soldState;
int count = 0;
public GumballMachine(int numberGumballs){
soldOutState = new SoldOutState(this);
noQuarterState = new NoQuarterState(this);
hasQuarterState = new HasQuaterState(this);
soldState = new SoldState(this);
this.count = numberGumballs;
if(numberGumballs > 0)
state = noQuarterState;
}
public void insertQuarter(){
//조건문이 전부 사라지고 현재 상태 객체에 위임
state.insertQuarter();
}
public void ejectQuarter(){
state.ejectQuarter();
}
public void turnCrank(){
state.turnCrank();
state.dispense();
}
..
//해당 메소드를 사용해 다른 객체에서 상태 변경 가능
void setState(State state){
this.state = state;
}
//..각 State 객체를 위한 getter 메소드 등등..
}
개선점
- 각 상태의 행동을 별개의 클래스로 국지화 시킴
- 관리하기 힘든 if선언문들을 삭제
- 각 상태를 변경에 대해선 닫혀있도록 하면서, 확장에 대해(기능 추가 등) 열려있도록 함 = OCP
- 훨씬 더 이해하기 좋은 베이스와 구조
+ 뽑기 기계에 당첨 기능 추가
1) WinnerState 클래스 생성
public class WinnerState implements State{
..
public void dispense(){
System.out.println("당첨을 축하드립니다! 알맹이를 하나 더 받으실 수 있어요.");
gumballMachine.releaseBall(); //알맹이를 하나 내보내는 메소드
if(gumballMachine.getCount() == 0){
gumballMachine.setState(gumballMachine.getSoldOutState());
}else{
gumballMachine.releaseBall(); //하나 더 내보냄
if(gumballMachine.getCount() == 0){
gumballMachine.setState(gumballMachine.getNoQuarterState());
}
else{
System.out.println("더 이상 알맹이가 없습니다");
gumballMachine.setState(gumballMachine.getSoldOutState());
}
}
}
}
2) HasQuarterState::turnCrank() 수정 - 당첨 처리
public class HasQuarterState implements State{
//난수 생성기
Random randomWinner = new Random(System.currentTimeMillis());
..
public void turnCrank(){
System.out.println("손잡이를 돌리셨습니다");
int winner = randomWinner.nextInt(10);
//당첨 && 알맹이 개수 2개 이상인 경우
if(winner == 0 && gumballMachine.getCount() > 1){
gumballMachine.setState(gumballMachine.getWinnerState());
}else{
gumballMachine.setState(gumballMachine.getSoldState());
}
}
}
Q. WinnerState가 꼭 있어야 하는지? 그냥 SoldState에서 처리하면 안 되는지?
- 문제는 없지만 한 상태 클래스에서 두 가지 상태를 처리한다는 단점이 있음
- 코드 중복은 줄일 수 있지만 상태 클래스가 조금 불분명해짐 - 단일 역할 원칙도 위배
Q. SoldState와 WinnerState 간의 중복 코드 처리
- State를 추상클래스로 만들고 기본 기능을 추가하면 될 것 같음
- 또한 기본 구현에 예외처리 선언을 해두면 특정 상태에서 부적절한 메소드를 호출했을 때 별도 구현 없이 처리 될 것(상속)
Q. dispense() 메소드는 항상 호출됨. 부적절한 상황에서 조차
- 위의 방법을 사용할 수 도 있고, turnCrank()에서 부울 값을 리턴해서 처리해도 됨
연필을 깎으며
page 434
- A B C D E F
NOTE
- GumballMachine 인스턴스가 여러 개일 때, 변경점에 대해 생각해보기
- 상태 전환이 상태 클래스 내에만 있는 것에 대한 장단점 생각해보기
'프로그래밍 > 공부' 카테고리의 다른 글
Head First Design Patterns : (12)컴파운드 패턴 (0) | 2020.10.07 |
---|---|
Head First Design Patterns : (11)프록시 패턴 (0) | 2020.10.05 |
Head First Design Patterns : (9)이터레이터와 컴포지트 패턴 (0) | 2020.09.24 |
Head First Design Patterns : (8)템플릿 메소드 패턴 (0) | 2020.09.23 |
Head First Design Patterns : (7)어댑터 패턴과 퍼사드 패턴 (0) | 2020.09.21 |
Comments