1. 리팩토링
1) 분류 코드를 상태/전략 패턴으로 치환
- 이 리팩토링은 상태(전략)패턴이라는 디자인 패턴을 이용하여 리팩토링 한다.
2) 리팩토링 카탈로그
이름 | 분류 코드를 상태/전략 패턴으로 치환 |
상황 | 분류 코드마다 객체가 다른 동작을 함 |
문제 | 동작을 switch문으로 나누고 있지만 분류 코드가 동적으로 변하므로 분류코드를 하위 클래스로 치환은 사용 불가 |
해법 | 분류 코드를 나타내는 새로운 클래스를 작성해서 상태/전략 패턴을 사용함 |
결과 | o 분류 코드 타입 판별이 가능해짐 o 분류 코드에 따른 클래스 동작을 다형성으로 해결 가능 x 클래스 개수가 늘어남 |
2. 예제 프로그램
클래스명 | 역할 |
Logger | 로깅 클래스 |
State | 상태 객체를 나타내는 추상 클래스 |
StateStopped | STATE_STOPPED에 대응하는 추상 클래스 |
StateLogging | STATE_LOGGING에 대응하는 추상 클래스 |
Main | Logger 사용 예제 클래스 |
1) 리팩토링 전
public class Logger {
public static final int STATE_STOPPED = 0;
public static final int STATE_LOGGING = 1;
private int _state;
public Logger() {
_state = STATE_STOPPED;
}
public void start() {
switch (_state) {
case STATE_STOPPED:
System.out.println("** START LOGGING **");
_state = STATE_LOGGING;
break;
case STATE_LOGGING:
/* 아무것도 하지 않음 */
break;
default:
System.out.println("Invalid state: " + _state);
}
}
public void stop() {
switch (_state) {
case STATE_STOPPED:
/* 아무것도 하지 않음 */
break;
case STATE_LOGGING:
System.out.println("** STOP LOGGING **");
_state = STATE_STOPPED;
break;
default:
System.out.println("Invalid state: " + _state);
}
}
public void log(String info) {
switch (_state) {
case STATE_STOPPED:
System.out.println("Ignoring: " + info);
break;
case STATE_LOGGING:
System.out.println("Logging: " + info);
break;
default:
System.out.println("Invalid state: " + _state);
}
}
}
public class Main {
public static void main(String[] args) {
Logger logger = new Logger();
logger.log("information #1");
logger.start();
logger.log("information #2");
logger.start();
logger.log("information #3");
logger.stop();
logger.log("information #4");
logger.stop();
logger.log("information #5");
}
}
2) 리팩토링 실행
① 상태 객체를 나타내는 클래스 작성
⑴ 분류 코드를 자기 캡슐화
⑵ 분류 코드를 나타내는 새로운 클래스 작성
⑶ 분류 코드 값마다 상태 객체의 하위 클래스 작성
⑷ 분류 코드를 얻는 추상 메서드를 상태 객체에 작성
⑸ 하위 클래스는 추상 메서드를 오버라이드해서 분류 코드를 반환
⑹ 컴파일해서 테스트
② 상태 객체 사용
⑴ 분류 코드를 사용하는 클래스에 상태 객체용 필드 추가
⑵ 분류 코드를 조사하는 코드를 분류 코드를 얻는 메서드 호출로 치환
⑶ 분류 코드를 변경하는 코드를 상태 객체를 변경하는 코드로 치환
⑷ 컴파일해서 테스트
3) 리팩토링 후
public abstract class State {
public abstract int getTypeCode();
}
public class StateStopped extends State {
@Override public int getTypeCode() {
return Logger.STATE_STOPPED;
}
}
public class StateLogging extends State {
@Override public int getTypeCode() {
return Logger.STATE_LOGGING;
}
}
public class Logger {
public static final int STATE_STOPPED = 0;
public static final int STATE_LOGGING = 1;
private State _state;
public Logger() {
setState(STATE_STOPPED);
}
public int getState() {
return _state.getTypeCode();
}
public void setState(int state) {
switch (state) {
case STATE_STOPPED:
_state = new StateStopped();
break;
case STATE_LOGGING:
_state = new StateLogging();
break;
default:
System.out.println("Invalid state: " + state);
}
}
public void start() {
switch (getState()) {
case STATE_STOPPED:
System.out.println("** START LOGGING **");
setState(STATE_LOGGING);
break;
case STATE_LOGGING:
/* 아무것도 하지 않음 */
break;
default:
System.out.println("Invalid state: " + getState());
}
}
public void stop() {
switch (getState()) {
case STATE_STOPPED:
/* 아무것도 하지 않음 */
break;
case STATE_LOGGING:
System.out.println("** STOP LOGGING **");
setState(STATE_STOPPED);
break;
default:
System.out.println("Invalid state: " + getState());
}
}
public void log(String info) {
switch (getState()) {
case STATE_STOPPED:
System.out.println("Ignoring: " + info);
break;
case STATE_LOGGING:
System.out.println("Logging: " + info);
break;
default:
System.out.println("Invalid state: " + getState());
}
}
}
3. 코드 추가 수정
1) enum 사용
2) 상태 의존 코드를 상태 객체로 이동
3) 코드 추가 수정
public class Logger {
private enum State {
STOPPED {
@Override public void start() {
System.out.println("** START LOGGING **");
}
@Override public void stop() {
/* 아무것도 하지 않음 */
}
@Override public void log(String info) {
System.out.println("Ignoring: " + info);
}
},
LOGGING {
@Override public void start() {
/* 아무것도 하지 않음 */
}
@Override public void stop() {
System.out.println("** STOP LOGGING **");
}
@Override public void log(String info) {
System.out.println("Logging: " + info);
}
};
public abstract void start();
public abstract void stop();
public abstract void log(String info);
}
private State _state;
public Logger() {
setState(State.STOPPED);
}
public void setState(State state) {
_state = state;
}
public void start() {
_state.start();
setState(State.LOGGING);
}
public void stop() {
_state.stop();
setState(State.STOPPED);
}
public void log(String info) {
_state.log(info);
}
}
* enum 사용 안하기
interface State{
public void start(Logger logger);
public void stop(Logger logger);
public void log(String info);
}
class Stopped implements State {
@Override
public void start(Logger logger) {
System.out.println("** START LOGGING **");
logger.setState(new Logging());
}
@Override
public void stop(Logger logger) {}
@Override
public void log(String info) {
System.out.println("Ignoring: " + info);
}
}
class Logging implements State {
@Override
public void start(Logger logger) {}
@Override
public void stop(Logger logger) {
System.out.println("** STOP LOGGING **");
logger.setState(new Stopped());
}
@Override
public void log(String info) {
System.out.println("Logging: " + info);
}
}
public class Logger{
private State _state;
public Logger() {
setState(new Stopped());
}
public void setState(State state) {
_state = state;
}
public void start() {
_state.start(this);
}
public void stop() {
_state.stop(this);
}
public void log(String info) {
_state.log(info);
}
}
4. 한 걸음 더 나아가기
1) 분류 코드를 치환하는 세 가지 방법 비교
- 분류 코드를 클래스로 치환
- 분류 코드를 하위 클래스로 치환 : 분류 코드마다 클래스 동작이 다를 때 사용하는 리팩토링
- 분류 코드를 상태/전략 패턴으로 치환 : 분류 코드에 따라 클래스 동작도 다르고 분류 코드를 하위 클래스로 표현할 수 없을 때 사용하는 리팩토링
2) 상태 패턴과 전략 패턴의 차이
- 상태 패턴 : 프로그램 상태를 객체로 표현하고 상태에 의존하는 코드를 하위 클래스 메서드에 작성하는 것
- 전략 패턴 : 하나로 정리된 처리를 하는 알고리즘을 조용히 전환할 때 사용하는 패턴
3) 다형적 해결로 default 제거
- 상태 패턴을 사용하여 다형적 해결을 적용하면 default에 해당하는 코드가 필요 없다.
'Major > Java' 카테고리의 다른 글
[자바로 배우는 리팩토링 입문] 10장 에러 코드를 예외로 치환 (0) | 2022.06.08 |
---|---|
[자바로 배우는 리팩토링 입문] 8장 분류 코드를 하위 클래스로 치환 (0) | 2022.06.07 |
[자바로 배우는 리팩토링 입문] 7장 분류 코드를 클래스로 치환 (0) | 2022.06.04 |
[자바로 배우는 리팩토링 입문] 6장 클래스 추출 (0) | 2022.06.02 |
[자바로 배우는 리팩토링 입문] 5장 메서드 추출 (0) | 2022.06.01 |