You will be fine

<자바로 배우는 리팩토링 입문> 7장~9장

by BFine
반응형

7장. 분류 코드를 클래스로 치환 

 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.  상태 패턴과 전략 패턴의 차이

  -  상태패턴

       => 프로그램 상태를 객체로 표현하고 상태에 의존하는 코드를 하위클래스 메서드로 작성

  -  전략패턴

       => 입출력을 인터페이스로 규정하고 구체적인 클래스를 선언

 

 

출처 : www.gilbut.co.kr/book/view?bookcode=BN001847  

반응형

블로그의 정보

57개월 BackEnd

BFine

활동하기