1. 리팩토링
1) 분류 코드를 하위 클래스로 치환
- 분류코드의 종류에 따라 다른 동작을 한다면, 하위 클래스로 치환하는 리팩토링이 좋다
- 상위 클래스에 선언한 메서드를 오버라이드해서 다형적 메서드로 만드는 것이 핵심
2) 리팩토링 카탈로그
이름 | 분류 코드를 하위 클래스로 치환 |
상황 | 분류 코드마다 객체가 다른 동작을 함 |
문제 | switch 문을 써서 동작을 구분함 |
해법 | 분류 코드를 하위 클래스로 치환해서 다형적 메서드를 작성함 |
결과 | o 동작이 클래스별로 나뉨 x 클래스 개수가 늘어남 |
3) 구조와 동작
- 구조 : 프로그램의 정적인 성질
- 동작 : 프로그램의 동적인 성질
👉 분류 코드를 하위 클래스로 치환 리팩토링은 하위 클래스라는 '구조'를 새로 만들거나,
switch 문 하나에 모여있던 '동작'을 하위 클래스로 배분한다.
2. 예제 프로그램
클래스명 | 역할 |
Shape | 도형(직선, 사각형, 타원)을 나타내는 클래스 |
ShapeLine | 직선을 나타내는 클래스 |
ShapeRectangle | 사각형을 나타내는 클래스 |
ShapeOval | 타원을 나타내는 클래스 |
Main | 도형을 사용하는 클래스 |
1) 리팩토링 전
public class Shape {
public static final int TYPECODE_LINE = 0;
public static final int TYPECODE_RECTANGLE = 1;
public static final int TYPECODE_OVAL = 2;
private final int _typecode;
private final int _startx;
private final int _starty;
private final int _endx;
private final int _endy;
protected Shape(int typecode, int startx, int starty, int endx, int endy) {
_typecode = typecode;
_startx = startx;
_endx = endx;
_starty = starty;
_endy = endy;
}
public int getTypecode() { return _typecode; }
public String getName() {
switch (_typecode) {
case TYPECODE_LINE:
return "LINE";
case TYPECODE_RECTANGLE:
return "RECTANGLE";
case TYPECODE_OVAL:
return "OVAL";
default:
return null;
}
}
public String toString() {
return "[ "
+ getName() + ", "
+ "(" + _startx + ", " + _starty + ")-"
+ "(" + _endx + ", " + _endy + ") ]";
}
public void draw() {
switch (_typecode) {
case TYPECODE_LINE:
drawLine();
break;
case TYPECODE_RECTANGLE:
drawRectangle();
break;
case TYPECODE_OVAL:
drawOval();
break;
default:
;
}
}
private void drawLine() {
System.out.println("drawLine: " + this.toString());
// ...
}
private void drawRectangle() {
System.out.println("drawRectangle: " + this.toString());
// ...
}
private void drawOval() {
System.out.println("drawOval: " + this.toString());
// ...
}
}
public class Main {
public static void main(String[] args) {
Shape line = new Shape(Shape.TYPECODE_LINE, 0, 0, 100, 200);
Shape rectangle = new Shape(Shape.TYPECODE_RECTANGLE, 10, 20, 30, 40);
Shape oval = new Shape(Shape.TYPECODE_OVAL, 100, 200, 300, 400);
Shape[] shape = {
line, rectangle, oval
};
for (Shape s : shape) {
s.draw();
}
}
}
2) 리팩토링 실행
① 분류 코드에 대응하는 하위 클래스 작성
⑴ 분류 코드를 자기 캡슐화
⑵ 분류 코드를 바탕으로 인스턴스를 작성하고 있다면 팩토리 메서드 작성
* 팩토리 메서드는 만들 인스턴스 클래스명을 은폐하려고 사용함
⑶ 분류 코드 값마다 하위 클래스 작성
⑷ 컴파일 해서 테스트
② 불필요한 필드 삭제
⑴ 분류 코드 필드 삭제
⑵ 기존 클래스의 분류 코드 게터 메서드를 추상 메서드로 만듦
⑶ 컴파일해서 테스트
3) 리팩토링 후
package after;
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;
private final int _startx;
private final int _starty;
private final int _endx;
private final int _endy;
public static Shape createShape(int typecode, int startx, int starty, int endx, int endy) {
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 Shape(int startx, int starty, int endx, int endy) {
_startx = startx;
_endx = endx;
_starty = starty;
_endy = endy;
}
public abstract int getTypecode();
public abstract String getName();
public String toString() {
return "[ "
+ getName() + ", "
+ "(" + _startx + ", " + _starty + ")-"
+ "(" + _endx + ", " + _endy + ") ]";
}
public abstract void draw();
}
package after;
public class ShapeLine extends Shape {
protected ShapeLine(int startx, int starty, int endx, int endy) {
super(startx, starty, endx, endy);
}
@Override public int getTypecode() { return Shape.TYPECODE_LINE; }
@Override public String getName() { return "LINE"; }
@Override public void draw() { drawLine(); }
private void drawLine() {
System.out.println("drawLine: " + this.toString());
// ...
}
}
package after;
public class ShapeOval extends Shape {
protected ShapeOval(int startx, int starty, int endx, int endy) {
super(startx, starty, endx, endy);
}
@Override public int getTypecode() { return Shape.TYPECODE_OVAL; }
@Override public String getName() { return "OVAL"; }
@Override public void draw() { drawOval(); }
private void drawOval() {
System.out.println("drawOval: " + this.toString());
// ...
}
}
package after;
public class ShapeRectangle extends Shape {
protected ShapeRectangle(int startx, int starty, int endx, int endy) {
super(startx, starty, endx, endy);
}
@Override public int getTypecode() { return Shape.TYPECODE_RECTANGLE; }
@Override public String getName() { return "RECTANGLE"; }
@Override public void draw() { drawRectangle(); }
private void drawRectangle() {
System.out.println("drawRectangle: " + this.toString());
// ...
}
}
package after;
public class Main {
public static void main(String[] args) {
Shape line = Shape.createShape(Shape.TYPECODE_LINE, 0, 0, 100, 200);
Shape rectangle = Shape.createShape(Shape.TYPECODE_RECTANGLE, 10, 20, 30, 40);
Shape oval = Shape.createShape(Shape.TYPECODE_OVAL, 100, 200, 300, 400);
Shape[] shape = {
line, rectangle, oval
};
for (Shape s : shape) {
s.draw();
}
}
}
3. 한 걸음 더 나아가기
1) switch문과 instanceof 연산자가 풍기는 악취
: 다형적 메서드로 해결하는 방안을 검토해야 함
2) 객체 생성 switch 문 삭제
- 팩토리 메서드를 또 만들어서 line, oval, rectangle이 상속받게 한다.
* 균형을 생각하여 리팩토링을 해야한다.
3) 팩토리 메서드 여러 개 준비하기
- 처음부터 클래스에 대한 팩토리 메서드를 각각 만들어서 리팩토링한다.
4) 어디까지 리팩토링해야 되나
정답! 상황에 따라 다르다~
4. 정리
- 분류 코드 값에 따라 동작이 달라진다면 분류 코드 값마다 하위 클래스를 만들어서 동작을 배분한다.
'Major > Java' 카테고리의 다른 글
[자바로 배우는 리팩토링 입문] 10장 에러 코드를 예외로 치환 (0) | 2022.06.08 |
---|---|
[자바로 배우는 리팩토링 입문] 9장 분류 코드를 상태/전략 패턴 치환 (0) | 2022.06.08 |
[자바로 배우는 리팩토링 입문] 7장 분류 코드를 클래스로 치환 (0) | 2022.06.04 |
[자바로 배우는 리팩토링 입문] 6장 클래스 추출 (0) | 2022.06.02 |
[자바로 배우는 리팩토링 입문] 5장 메서드 추출 (0) | 2022.06.01 |