[자바로 배우는 리팩토링 입문] 9장 분류 코드를 상태/전략 패턴 치환

2022. 6. 8. 00:28·Major/Java
728x90

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에 해당하는 코드가 필요 없다.

 

728x90
저작자표시 비영리 (새창열림)

'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
'Major/Java' 카테고리의 다른 글
  • [자바로 배우는 리팩토링 입문] 10장 에러 코드를 예외로 치환
  • [자바로 배우는 리팩토링 입문] 8장 분류 코드를 하위 클래스로 치환
  • [자바로 배우는 리팩토링 입문] 7장 분류 코드를 클래스로 치환
  • [자바로 배우는 리팩토링 입문] 6장 클래스 추출
BeNI
BeNI
코딩하는 블로그
  • BeNI
    코딩못하는컴공
    BeNI
  • 전체
    오늘
    어제
    • Menu (253)
      • My profile (1)
      • 회고 | 후기 (8)
      • Frontend (65)
        • Article (11)
        • Study (35)
        • 프로그래머스 FE 데브코스 (19)
      • Backend (0)
      • Algorithm (58)
        • Solution (46)
        • Study (12)
      • Major (111)
        • C&C++ (23)
        • Java (20)
        • Data Structure (14)
        • Computer Network (12)
        • Database (15)
        • Linux (6)
        • Architecture (3)
        • Lisp (15)
        • OS (1)
        • Security (2)
      • etc (2)
  • 링크

    • 깃허브
    • 방명록
  • 인기 글

  • 최근 댓글

  • 최근 글

  • 태그

    lisp
    백준
    C++
    자료구조
    프로그래머스
    Algorithm
    react
    데브코스
    리팩토링
    파일처리
  • hELLO· Designed By정상우.v4.10.2
BeNI
[자바로 배우는 리팩토링 입문] 9장 분류 코드를 상태/전략 패턴 치환
상단으로

티스토리툴바