스테이트(State) 패턴

|

스테이트(Command) 패턴은 오브젝트의 상태(State)를 클래스화한 패턴입니다.

스테이트 패턴을 사용하지 않았을 경우 만약 각 상태마나 다른 동작을 하게 하기 위해서 if 문을 사용했다면,

public void work() {
  if(현재 상태 == 낮){
    낮에 할 일();
  }
  else if(현재 상태 == 밤){
    밤에 할 일();
  }
}

와 같은 형태의 코드가 됩니다. 각 함수마다 현재 상태에 따라 분기문을 타게 되면, 향후 함수의 개수가 증가할 때마다(또는 상태가 증가할 때마다) 유지 보수가 힘들어지는 경우가 발생할 수 있습니다.

이런 경우 State 패턴을 사용하면 코드를 깔끔하게 관리할 수 있습니다. State 패턴의 UML은 다음과 같습니다.

Image


예제 코드

예제를 살펴 보도록 하겠습니다. 여기서는 아까의 예제와 같이 낮/밤이라는 ‘상태’를 각각 클래스화하도록 할 것입니다. 그리고 낮에는 이미지 갤러리, 밤에는 뮤직 프로그램이 실행되는 프로그램을 생각해보도록 하겠습니다.

일단, 다음과 같은 인터페이스를 만들어보겠습니다.

public interface State {

  public void setTime(ContextManager cm, int hour);

  public void setItem(Item item);

  public void show();

  public void close();
}

그리고, 각 상태는 다음과 같이 구현할 수 있습니다. (여기서는 각 State를 Singleton으로 구현을 했는데, 이 부분은 자유롭게 구현해도 됩니다. 다만 잦은 State 변경에 의해서 State를 재생성해야할 경우가 많다면, State를 매번 생성해야 할 필요가 있는지는 고민해보는 것이 좋을 것 같습니다.)

public class DayState implements State {

  private static DayState mInstance = new DayState();
  private ImageGallery mImageGallery = new ImageGallery();

  private DayState() {
  }

  public static State getInstance() {
    return mInstance;
  }

  @Override
  public void setTime(ContextManager cm, int hour) {
    if((hour < 9) || (hour >= 17)) {
      cm.setState(NightState.getInstance());
    }
    cm.setTime(hour);
  }

  @Override
  public void setItem(Item item) {
    mImageGallery.setItem(item);
  }

  @Override
  public void show() {
    mImageGallery.show();
  }

  @Override
  public void close() {
    mImageGallery.close();
  }

}


public class NightState implements State {

  private static NightState mInstance = new NightState();
  private MusicLibrary mMusicLibrary = new MusicLibrary();

  private NightState() {
  }

  public static State getInstance() {
    return mInstance;
  }

  @Override
  public void setTime(ContextManager cm, int hour) {
    if((hour >= 9) && (hour < 17)) {
      cm.setState(DayState.getInstance());
    }
    cm.setTime(hour);
  }

  @Override
  public void setItem(Item item) {
    mMusicLibrary.setItem(item);
  }

  @Override
  public void show() {
    mMusicLibrary.show();
  }

  @Override
  public void close() {
    mMusicLibrary.close();
  }
}

커맨드(Command) 패턴

|

커맨드(Command) 패턴을 살펴겠습니다. 나중에 추가로 포스팅하겠지만, 먼저 다른 패턴들과 간단히 비교하면 다음과 같습니다.

  • 스테이트(State) 패턴 : 상태 그 자체를 클래스화해서 사용
  • 스트래터지(Strategy) 패턴 : 알고리즘 자체를 클래스화해서 사용
  • 컴포지트(Composite) 패턴 : 각 객체들을 동일시화해서 사용
  • 커맨드(Command) 패턴 : 명령 그 자체를 클래스화해서 사용하는 패턴


커맨드 패턴의 UML은 다음과 같습니다.

Image


예제 코드

커맨드 패턴의 예제는 다음과 같습니다.

public interface Command {

  public void execute();
}

이와 같은 인터페이스를 하나 구현하고, 각 명령을 구현하는 클래스들은 이 인터페이스를 구현(상속)하면 됩니다. 예를 들면 다음과 같습니다.


public class DrawCommand implements Command {

  protected Drawable mDrawable;
  private Point mPosition;

  public DrawCommand(Drawable drawable, Point position) {
    mDrawable = drawable;
    mPosition = position;
  }

  @Override
  public void execute() {
    mDrawable.draw(mPosition.x, mPosition.y);
  }
}


다음과 같이 Macro 형태의 Command도 만들 수 있습니다. Command들의 리스트를 이용하면 되는데, Stack, Queue, List 등 어떤 리스트든 성격에 따라 자유롭게 사용해도 됩니다. (여기서는 Undo, Redo 기능처럼 보이게 하기 위해 Stack을 사용했습니다.)

public class MacroHandler implements Command {

  private Stack<Command> mCommandStack = new Stack<Command>();

  @Override
  public void execute() {
    Iterator<Command> it = mCommandStack.iterator();
    while(it.hasNext()) {
      ((Command) it.next()).execute();
    }
  }

  public boolean append(Command cmd) {
    // 만약 자기 자신이 추가(append)되었을 경우 execute()에서
    // 무한 루프로 빠질 가능성이 있기 때문에 미리 확인함
    if(cmd != this) {
      mCommandStack.push(cmd);

      return true;
    }

    return false;
  }

  public void undo() {
    if(mCommandStack.empty() == false) {
      mCommandStack.pop();
    }
  }

  public void clear() {
    mCommandStack.clear();
  }
}

옵저버(Observer) 패턴

|

가장 많이 사용 되는 패턴 중 하나가 옵저버 패턴(Observer Pattern)입니다. 아마 디자인 패턴을 잘 모르더라도 자신도 모르게 이미 옵저버 패턴을 사용하고 있는 경우가 대부분일 것 같습니다. 예를 들어, 안드로이드 개발을 할 때 Button에 OnClickEventListener를 등록하는 것들이 옵저버(Observer) 패턴에 해당됩니다. 평소엔 가만히 있다가 해당 버튼이 클릭되었을 때 그 이벤트를 알려달라고 리스너를 등록하는 것입니다.

옵저버 패턴은 특정 인스턴스에 이벤트 리스너(EventListener)를 달고 대기하고 있다가 그 인스턴스에 이벤트가 발생하면 그 결과를 통보(Notify)받는 방식이며 UML로 표현하면 다음과 같습니다.

Image


예제 코드

예제 코드는 다음과 같이 간단히 작성할 수 있습니다.

public interface Observer {

  public void update(Subject subject, int event);
}
public abstract class Subject {

  private ArrayList<Observer> mObserverList = new ArrayList<Observer>();

  public void add(Observer observer) {
    mObserverList.add(observer);
  }

  public void Remove(Observer observer) {
    mObserverList.remove(observer);
  }

  public void notify(int event) {
    for(Observer observer : mObserverList) {
      observer.update(this, event);
    }
  }
}

싱글톤(Singleton) 패턴

|

프로그램에서 단 하나의 인스턴스(Instance)만 존재하게 하고 싶을 때 사용하는 패턴입니다. 어디서든지 그 인스턴스에 접근할 수 있기 때문에 전역 변수 등을 관리할 때 상당히 편리하기 때문에 많은 사람들이 남발하고 있는 패턴이기도 합니다.

하지만, 싱글톤(Singleton)은 실제로는 단순 전역 변수와 거의 같은 용도로 많이 쓰이며 그 성격상 객체 지향과는 거리가 있는 패턴입니다. 프로그램 어느 곳에서든 Singleton에 접근할 수 있기 때문에 정보의 보호도 되지 않으며, 누가 어떤 값을 건드렸는지 추적하기도 쉽지 않습니다. 따라서 가급적 사용하지 않는 것이 좋지만 적절하게 제한적을 사용하면 편리하긴 합니다.

#UML

싱글톤 패턴의 UML은 다음과 같습니다. 클래스 하나 뿐이기 때문에 UML이 단순합니다.

Image


예제 코드

싱글톤 패턴을 코드로 구현하면 다음과 같습니다. (다만, 이 코드는 멀티쓰레드(Multi-Thread) 환경에서 문제가 발생하는 코드입니다. 해결법이 여러가지인데, 단계별로 해결된 코드를 설명해나가도록 하겠습니다.)

public class Singleton {

  private static Singleton mInstance = null;

  private Singleton() {
  }

  public static Singleton getInstance() {
    if(mInstance == null) {
      mInstance = new Singleton();
    }

    return mInstance;
  }
}


위 코드는 멀티쓰레드 환경에서 getInstace() 메소드가 끝나기 전에 여러 쓰레드에서 동시에 접근을 할 수 있기 때문에 재수가 없으면 Singleton 인스턴스가 여러 개 생성될 수 있습니다.


함수 전체에 synchronized 적용

이 경우 getInstance() 메소드를 synchronized로 동기화시켜 해결할 수 있습니다.

public class Singleton {

  private static Singleton mInstance = null;

  private Singleton() {
  }

  public synchronized static Singleton getInstance() {
    if(mInstance == null) {
      mInstance = new Singleton();
    }

    return mInstance;
  }
}


하지만, 함수 전체에 synchronized는 동기화 과정에서 속도 문제가 발생하고 성능 저하를 가져 올 수 있습니다. (물론, 위 예제 코드 정도로는 그렇게 치명적인 속도 문제가 발생할 가능성은 많지 않습니다.)


함수 내부 일부분에 synchronized 적용

그래서 함수 내부에 최소 구간에만 synchronized를 거는 방법도 있습니다.

public class Singleton {

  private static Singleton mInstance = null;

  private Singleton() {
  }

  public static Singleton getInstance() {
    if(mInstance == null) {
      synchronized(Singleton.class) {
        if(mInstance == null) {
          mInstance = new Singleton();
        }
      }
    }

    return mInstance;
  }
}


처음부터 인스턴스를 생성

그리고 코드를 좀 더 깔끔하고 쉽게 구현하기 위해서는 아예 처음부터 인스턴스를 생성해버리는 방법도 있습니다. 저는 처음부터 인스턴스를 생성하는 방법을 가장 많이 사용하고 있습니다. 구현도 쉽고 코드도 더 깔끔한 것 같아서입니다.

public class Singleton {

  private static Singleton mInstance = new Singleton();

  private Singleton() {
  }

  public static Singleton getInstance() {
    return mInstance;
  }
}

물론 이 경우는 프로그램이 처음 실행되면서 바로 인스턴스가 생겨버리기 때문에, 불필요한 부분에서 인스턴스가 메모리를 차지해버린다는 단점이 있습니다. 하지만, 그런 경우는 Singleton을 사용하지 않는게 더 적합한 경우가 많기 때문에 대부분의 Singleton 구현은 위의 마지막 예제처럼 구현하면 됩니다.

하지만 최선은 싱글톤 패턴을 사용하지 않거나 최소한으로 사용하는 거라고 생각합니다.

포인터 vs 참조형 타입

|

보통 C++ 개발자는 C 언어를 먼저 배우고 나서 C++을 배우는 경우가 많습니다.

C 언어에서 데이터 주소를 넘기는 방법은 포인터(Pointer)밖에 없습니다. 그러다보니 C++에서도 포인터로 데이터 주소를 넘기는 경우가 종종 있는데, 대부분의 경우는 포인터가 아닌 참조형(Reference) 타입으로 대체할 수 있습니다.

포인터대신 참조형 타입을 사용하면 좋은 점들은 다음과 같습니다.

참조형 타입의 장점

참조형 타입은 포인터보다 안전합니다.

왜냐하면 메모리 주소를 직접 다루지 않기 때문에 nullptr 같은 경우가 발생할 수가 없습니다.


코드 스타일이 좋아집니다.

‘*’ 이나 ‘&’와 같은 심볼을 사용하지 않아도 됩니다.

대신 함수 원형을 보지 않고 호출부만 봤을 경우는 매개변수가 값이 복사되서 넘어가는지 주소값만 넘어가는지 알 수 없는 단점이 있습니다. 항상 함수의 선언부를 봐야 알 수 있습니다.


메모리의 오너십(Ownership)이 어디에 있는지 명확하게 해준다.

누군가가 만든 함수에 다른 프로그래머가 참조형으로 객체를 넘겨줄 경우, 그 함수 안에서는 그 객체를 메모리에서 해제할 수 없습니다. 만약 포인터로 넘겨받았을 경우는 메모리 해제의 책임이 변수를 생성한 사람에게 있는지, 그 함수를 작성한 사람에게 있는지 명확하지 않습니다.


미세하지만 참조형 타입이 포인터보다 성능이 더 좋다.

큰 차이는 없습니다. 다만 참조형 타입은 포인터처럼 주소값 복사의 과정이 없기 때문에 성능면에서는 조금 더 좋습니다.