State 패턴

5.디자인패턴 2016. 8. 15. 18:24

참고

- 상태와 행위의 결별


상태 변수는 어떤 경우에도 좋지 않다. 상태 변수는 변수와 행위와의 결합을 만들어 내고, 이 과정에서 조건문들을 부수적으로 생산해 낸다. 따라서 언어적으로 허용되는 한 상태 변수는 최대한 없애주는 것이 좋다.


해결하고자 하는 문제

- 상태 변수에 의해 행위가 변경된다.
- 이 상태 변수가 행위를 변경하기 위해서 조건문을 사용한다.
- 상태 변수를 체크하기 위한 조건문이 너무 많다.

문제 코드

class Employee {

    public static final int ENGINEER = 1;

    public static final int MANAGER = 2;

    public static final int SALESMAN = 3;

    private int type;

    public void setType(int type){

        this.type = type

    }

    public Employee(int type){

        setType(type);

    }

    public int getAmount(){

        switch(type){

            case ENGINEER :

                return 100;

            case MANAGER :

                return 200;

            case SALESMAN :

                return 300;

        }

        return 0;

    }

} 

이 예제는 마틴 파울러의 "리팩토링"에서 사용된 예제이다. Employee 객체는 내부적으로 직원의 타입을 나타내기 위해 type 이라는 상태 변수를 사용하고 있다. 그리고 getAmount() 함수에서는 Employee의 type에 따라서 다른 결과값을 출력하도록 만들어져 있다.

이 소스에서 확인할 수 있는 것은 다음과 같다.

1. type 변수는 getAmount() 함수 내부에 switch - case의 각 구문과 1:1 매칭된다.

2. type 변수의 값에 따라서 다른 구문이 호출된다. ENGINEER 값일 경우 return 100, MANAGER일 경우 return 200이 실행된다는 말이다.


사실 이런 상황에서 type 변수는 오직 행위를 변경하고자 하는 목적에서 선언된 것이다. 만약 type을 통해 변경하고자 하는 행위를 직접 변경한다면 전체 소스는 매우 간결해지게 될 것이다.


이 목적을 달성하기 위해서는 일단 각 type 에 해당하는 객체를 구현하고, 각 객체별로 수행될 함수를 정의할 필요가 있다.


interface EmployeeType{

    public int getAmount();

}

class Engineer implements EmployeeType{

    public int getAmount() { return 100; }

}

class Manager implements EmployeeType{

    public int getAmount() { return 200; }

}

class Salesman implements EmployeeType{

    public int getAmount() { return 300; }

}

이와 같이 ENGINEER, MANAGER, SALESMAN에 대응하는 객체를 구현하였다. 이 객체들은 EmployeeType이라는 인터페이스를 상속 받고 있으므로 EmployeeType으로 선언된 멤버 변수에 자유롭게 변경하여 집어 넣을 수 있다.


이제 기존의 type  변수 대신에 새로 정의한 EmployeeType을 사용한다.

private EmployeeType type;

public void setType(EmployeeType type){

    this.type = type

}

public Employee(EmployeeType type){

    setType(type);

}

이제 getAmount() 함수 부분을 수정해야 한다. 이미 EmployeeType의 하위 객체들은 getAmount() 함수를 가지고 있다. 그리고 새로 정의된 type 변수에는 적절한 EmployeeType 객체가 할당되어 있다. 따라서 getAmount() 함수에서는 EmployeeType의 하위 객체가 가지고 있는 getAmount() 함수를 호출해 주기만 하면 된다.

public int getAmount(){

   return type.getAmount();

}

보면 알겠지만 기존의 int 타입 변수를 사용했을 때에는 getAmount() 함수 내부가 switch 문으로 구성되어 있어서 가독성이 많이 떨어졌다. 그럴 수 밖에 없었던 것은 type이 상태 변수 역할을 했기 때문이다. 상태 변수는 행위를 직접 변경시킬 수 없다. 행위와 상태 변수가 직접 대응될 수 없기 때문이다. 그래서 상태 변수의 값을 검사하는 조건문이 추가 될 수 밖에 없었다. 하지만 상태 변수 대신 행위를 직접 수행할 수 있는 객체에 대한 레퍼런스를 사용하면 조건문을 별도로 사용할 필요가 없다.


최종 결과

interface EmployeeType{

    public int getAmount();

}

class Engineer implements EmployeeType{

    public int getAmount() { return 100; }

}

class Manager implements EmployeeType{

    public int getAmount() { return 200; }

}

class Salesman implements EmployeeType{

    public int getAmount() { return 300; }

}

class Employee {

    private EmployeeType type;

    public void setType(EmployeeType type){ this.type = type; }

    public Employee(EmployeeType type){

        setType(type);

    }

    public int getAmount(){

        return type.getAmount();

    }

}


State 패턴 클래스 다이어그램


Posted by 이세영2
,