[자바로 배우는 리팩토링 입문] 8장 분류 코드를 하위 클래스로 치환

2022. 6. 7. 20:10·Major/Java
728x90

 

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. 정리

- 분류 코드 값에 따라 동작이 달라진다면 분류 코드 값마다 하위 클래스를 만들어서 동작을 배분한다.

 

 

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

'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
'Major/Java' 카테고리의 다른 글
  • [자바로 배우는 리팩토링 입문] 10장 에러 코드를 예외로 치환
  • [자바로 배우는 리팩토링 입문] 9장 분류 코드를 상태/전략 패턴 치환
  • [자바로 배우는 리팩토링 입문] 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)
  • 링크

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

  • 최근 댓글

  • 최근 글

  • 태그

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

티스토리툴바