무상태 프로그래밍을 위해서 객체가 갖추어야 할 것은 두가지이다.


1. 비밀을 유지하라

2. 독립적이 되어라


비밀을 유지하라

간단하게 말하면 상태 확인 메소드를 제공하지 않아야 한다. getter 함수를 제공하지 말라는 얘기가 아니다. 물론 getter 함수도 최대한 자제해야 하는 것이긴 하지만 그보다 더 큰 문제를 발생시키는 것은 상태를 알려주는 메소드이다. 상태를 알려주면 상대 객체는 그 객체 상태에 종속적인 행위를 하게 된다. 이것을 막으려면 상대가 상태를 몰라도 자신에게 책임을 맡길 수 있도록 디자인 해야 한다.


가장 먼저 생각해야 하는 부분은 변수를 선언하는 것이다. 변수를 선언하는 이유를 곰곰히 생각해 봐야 한다. 그 변수가 꼭 필요한 것인지, 꼭 필요하다면 단순히 값을 저장하려는 목적인지, 아니면 상태를 표시하려는 목적인지 생각해 봐야 한다. 만약 조금이라도 값이 상태 처럼 사용된다면, 즉 변수의 값에 따라 동작을 바꿔야 하고, 그러기 위해서 조건문을 써야 한다면 바로 객체로 변환할 방법을 생각해 봐야 한다.


두번째는 그 변수값을 어떻게 사용할 것인지이다. 만약 getter를 제공해서 외부로 내보내야 한다면 외부 객체들은 그 변수를 어떤 용도로 사용하게 될지를 생각해야 한다. 만약 getter를 제공해서 외부에 있는 객체들이 자신의 상황을 알게 해야 하는 것이라면 객체의 설계에 대해서 다시 생각해 봐야 한다.


독립적이 되어라

상대의 상태를 보고 행위하려고 하지 말아야 한다. 뭔가를 해야 한다면 바로 해야 한다. 상대 객체의 상태를 보려고 하다보면 조건문이 생기고 코드는 복잡해진다.




둘 중 어느 것이 중요하냐고 물어본다면 당연히 비밀을 유지하는 쪽이다. 아무것도 제공하지 않는데 사용할 수는 없다. 중요한 부분은 비밀을 유지하면서도 기능을 구현할 수 있는 방법은 무엇인가 이다.


이미 한가지 예는 알려 주었다. 아래 코드는 온도에 관한 코드이다.


class Temperature{

    int temperature;

    public static final int HOT = 0;

    public static final int NORMAL = 1;

    public static final int COLD = 2;

    public void setTemperature(int temperature){

        this.temperature = temperature;

    }

    public int getState(){

        if(temperature > 30) return HOT;

        if(temperature >= 0) return NORMAL;

        else return COLD;

    }

}


이 코드는 자기 상태를 외부에 자꾸 알려 주도록 되어 있었다.(getState() 함수) 이로 인해 다른 객체들은 이 객체의 상태를 확인하지 않고서는 동작할 수 없었다. 하지만 이 Temperature 객체를 상태별로 분리한 후 전체 소스에서 이 객체의 상태를 확인하지 않고도 동작이 가능하게 변경되었다. 이것을 디자인 패턴에서 상태 패턴이라고 부른다.

문제점을 해결한 아래의 소스는 전형적인 상태 패턴이다.


interface ITemperature{

    public String toDbString();

    public void operateThermostat(Thermostat thermostat);

}

class HotTemperature implements ITemperature{

    String dbString = "HOT";

    public String toDbString(){ return dbString;}

    public void operateThermostat(Thermostat thermostat){ thermostat.cooling(); }

}

class NormalTemperature implements ITemperature{

    String dbString = "NORMAL";

    public String toDbString(){ return dbString;}

    public void operateThermostat(Thermostat thermostat){thermostat.stop();}

}

class ColdTemperature implements ITemperature{

    String dbString = "COLD";

    public String toDbString(){ return dbString;}

    public void operateThermostat(Thermostat thermostat){ thermostat.cooling(); }

}


이 상태별 객체는 이용할 수도 있고 거꾸로 상태별 객체에 의해 다른 객체가 이용될 수도 있다. 상태별 객체를 이용하는 경우는 상태별 객체에 있는 메소드가 제공하는 정보를 이용하는 것만으로도 목표가 달성되는 경우이다. 전형적으로 toDbString() 객체가 그러하다. DataBase 클래스에서는 이 객체의 메소드 중에서 toDbString() 함수만 호출해서 그 결과를 얻으면 끝이다. 이 결과값을 가지고 다시 상태를 확인하려고 하지 않기 때문에 그것 만으로 충분하다.


약간 변경이 필요했던 부분은 Thermostat 부분이다. Thermostat은 상태를 값으로 이용하려 하기 보다 자신의 행위를 변경하려고 했었다. 자신의 행위 변경을 위해서 다른 객체의 상태가 필요하다는 것은 다른 객체의 상태에 종속적이라는 뜻이다. 따라서 다른 객체의 상태를 가지고 자기가 무언가를 하기 보다는 종속적인 방식, 즉 Thermostat 객체가 상태별 객체에 매개 변수로 넘겨지는 편이 더 자연스럽다. 따라서 operateThermostat() 메소드에 의해 Thermostat 객체의 행위가 호출되도록 하면 코드가 더 간결해진다.


무상태 프로그래밍에서는 다른 객체의 상태에 관심이 없다. 단지 자기가 할 일에 집중하면 된다. 만약 다른 객체의 상태를 꼭 확인해야 하는 경우라면 다른 객체에 종속적이라는 의미이다. 따라서 하고자 하는 일을 직접 하지 말고, 상대 객체에 메소드를 정의하고 매개 변수로 객체를 넘겨야 한다.

Posted by 이세영2
,