<자바로 배우는 리팩토링 입문> 7장~9장
by BFine7장. 분류 코드를 클래스로 치환
a. int 분류대신 클래스형태로 만들자
- int 분류코드의 문제점
1. 이상한 값이 될 수 있음 & 혼동될 수 있음
b. enum 클래스를 사용하자
- 책에는 꽤 길게 클래스타입의 분류코드를 설명하고 있지만 결국 enum 클래스를 사용하자!
8장. 분류 코드를 하위 클래스로 치환
a. 하위 클래스
- 분류 코드 종류에 따라 객체가 다른 동작을 할 경우 하위클래스를 사용하자
- 구조를 새로만들거나 동작을 하위클래스로 배분하는 것이 목적이다.
=> 구조 : 프로그램의 정적인 성질(객체구조), 동작: 프로그램의 동적인 성질(메서드 동작)
b. 리팩토링 전
- 책에서 말하는 악취가 나는 코드이다. 메서드까지 분기되어 코드가 지저분해졌다.
public class Shape{
...
public void draw() {
switch (_typecode) {
case TYPECODE_LINE:
drawLine();
break;
case TYPECODE_RECTANGLE:
drawRectangle();
break;
case TYPECODE_OVAL:
drawOval();
break;
default:;
}
}
...
}
c. 하위 클래스로 치환
- Shape 클래스를 상속한 하위클래스를 통해서 변경해보자
- abstract class로 메서드를 상속받아서 Override 하도록 한다.
- 추상 메서드로 지정하면 이건 하위클래스에서 구현해야하는 메서드 라는게 명확해진다.
public abstract class Shape {
public static final int TYPECODE_LINE = 0;
public static final int TYPECODE_RECTANGLE = 1;
public static final int TYPECODE_OVAL = 2;
...
public static Shape createShape( ... ) {
switch (typecode) {
case TYPECODE_LINE:
return new ShapeLine(startx, starty, endx, endy);
case TYPECODE_RECTANGLE:
return new ShapeRectangle(startx, starty, endx, endy);
case TYPECODE_OVAL:
return new ShapeOval(startx, starty, endx, endy);
default:
throw new IllegalArgumentException("typecode = " + typecode);
}
}
public abstract void draw();
}
public class ShapeLine extends Shape {
protected ShapeLine(int startx, int starty, int endx, int endy) {
super(startx, starty, endx, endy);
}
@Override public void draw() { drawLine(); }
private void drawLine() {
System.out.println("drawLine: " + this.toString());
// ...
}
}
public class ShapeRectangle extends Shape {
protected ShapeRectangle(int startx, int starty, int endx, int endy) {
super(startx, starty, endx, endy);
}
@Override public void draw() { drawRectangle(); }
private void drawRectangle() {
System.out.println("drawRectangle: " + this.toString());
// ...
}
}
public class ShapeOval extends Shape {
protected ShapeOval(int startx, int starty, int endx, int endy) {
super(startx, starty, endx, endy);
}
@Override public void draw() { drawOval(); }
private void drawOval() {
System.out.println("drawOval: " + this.toString());
// ...
}
}
d. swich 문과 instanceof 연산자의 문제
- instanceof 연산자를 사용해 객체를 조사하는건 객체 지향스럽지 않다.
- 위의 코드를 Factory 패턴을 활용하여 switch문을 변경해보자.
public abstract class Shape {
...
public static Shape createShape(ShapeFactory factory, ) {
return factory.create(startx, starty, endx, endy);
}
...
}
public abstract class ShapeFactory {
public abstract Shape create(int startx, int starty, int endx, int endy);
public static class LineFactory extends ShapeFactory {
private static final ShapeFactory factory = new LineFactory();
private LineFactory() {
}
public static ShapeFactory getInstance() {
return factory;
}
public Shape create(int startx, int starty, int endx, int endy) {
return new ShapeLine(startx, starty, endx, endy);
}
}
public static class RectangleFactory extends ShapeFactory {
private static final ShapeFactory factory = new RectangleFactory();
private RectangleFactory() {
}
public static ShapeFactory getInstance() {
return factory;
}
public Shape create(int startx, int starty, int endx, int endy) {
return new ShapeRectangle(startx, starty, endx, endy);
}
}
public static class OvalFactory extends ShapeFactory {
private static final ShapeFactory factory = new OvalFactory();
private OvalFactory() {
}
public static ShapeFactory getInstance() {
return factory;
}
public Shape create(int startx, int starty, int endx, int endy) {
return new ShapeOval(startx, starty, endx, endy);
}
}
}
Shape line = Shape.createShape(ShapeFactory.LineFactory.getInstance(),);
Shape rectangle = Shape.createShape(ShapeFactory.RectangleFactory.getInstance(),;
Shape oval = Shape.createShape(ShapeFactory.OvalFactory.getInstance(),);
- switch문을 제거하려다 오히려 지나치게 복잡해졌다. 메서드를 하나로만 하려고 하지말자.
public abstract class Shape {
private final int _startx;
private final int _starty;
private final int _endx;
private final int _endy;
public static ShapeLine createShapeLine(int startx, int starty, int endx, int endy) {
return new ShapeLine(startx, starty, endx, endy);
}
public static ShapeRectangle createShapeRectangle(int startx, int starty, int endx, int endy) {
return new ShapeRectangle(startx, starty, endx, endy);
}
public static ShapeOval createShapeOval(int startx, int starty, int endx, int endy) {
return new ShapeOval(startx, starty, endx, endy);
}
public abstract void draw();
}
Shape line = Shape.createShapeLine(0, 0, 100, 200);
Shape rectangle = Shape.createShapeRectangle(10, 20, 30, 40);
Shape oval = Shape.createShapeOval(100, 200, 300, 400);
e. 어디까지 리팩토링 해야할까
- 프로젝트 규모로 예를들면 소규모라면 switch문을 사용하더라도 제거하지 않는쪽이 알기 쉬움
거기에 기능 추가 예정도 없다면 굳이 switch문이 있어도 큰문제가 없다.
- 리팩토링은 꼭 해야 한다고 단정하는게 아닌 상황에 따라 적절히 판단하는 게 중요하다.
다. 분류코드를 상태/전략 패턴으로 치환
a. 상태/전략 패턴
- 이 디자인 패턴은 분류코드를 상태객체라고 부르는 객체를 사용해 치환
b. 리팩토링 전
- 이 예제는 객체 상태에 따라 처리 내용이 변함
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 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);
}
}
}
c. 리팩토링 후
- enum 클래스를 활용하여 상태에 따라서 메서드를 분기처리 한다.
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);
}
}
d. 상태 패턴과 전략 패턴의 차이
- 상태패턴
=> 프로그램 상태를 객체로 표현하고 상태에 의존하는 코드를 하위클래스 메서드로 작성
- 전략패턴
=> 입출력을 인터페이스로 규정하고 구체적인 클래스를 선언
'개발서적 > 자바로 배우는 리팩토링 입문' 카테고리의 다른 글
<자바로 배우는 리팩토링 입문> 13장~15장 (0) | 2021.03.23 |
---|---|
<자바로 배우는 리팩토링 입문> 10장~12장 (0) | 2021.03.21 |
<자바로 배우는 리팩토링 입문> 4장~6장 (0) | 2021.03.16 |
<자바로 배우는 리팩토링 입문> 1장~3장 (0) | 2021.03.14 |
블로그의 정보
57개월 BackEnd
BFine