State Pattern(상태 패턴)?
상태변화를 가지는 주요 객체를 다룰 때 유용하게 적용해 볼 수 있는 패턴이다.
의도
객체의 내부 상태에 따라 스스로 행동을 변경할 수 있게 허가하는 패턴으로 이렇게 하면 개체는 마치 자신의 클래스를 바꾸는 것처럼 보인다.
간단한 예를 통하여 상태 패턴에 대하여 알아보도록 하자.
A, B, C상태가 있고 이 상태에는 a(), b(), c() 메서드를 가진 다고 가정을 해보자. A상태의 메서드인 a()를 실행하면 B상태가 되고, B상태의 b()메서드를 이용하면 C상태가 된다. 마찬가지로 C상태의 c()메서드를 실행하면 A상태가 된다.
이러한 모듈들이 여러 개 있다고 가정하여보자.
이를 사용 하기 위해서는 상태변화에 대해 완벽한 지식을 가지고 있어야 시스템의 메서드를 제대로 활용 할 수 있을 것이다. 상태변화에 대한 지식이 있더라도 상태를 체크하지 않았다거나, 변경시키지 않는 등의 실수를 하면 시스템에 보이지 않는 구조적인 에러가 발생 할 수 있다.
if(시스템 상태 == A)
{
시스템.a();
시스템 상태 = B;
}
else
{
시스템 에러;
}
… … |
이와 같은 시스템 구조에 상태 패턴을 적용 하면, 모듈을 구축하는 팀이 시스템의 상태에 전혀 신경을 쓰지 않고도, 상태 체크와 상태변경이 완벽하게 처리되도록 만들 수 있다.
구조
참여자
r Context
사용자가 관심 있는 인터페이스를 정의 합니다. 개체의 현재 상태를 정의한 서브클래스의 인스턴스를 유지, 관리 합니다.
r State
Context의 각 상태 별로 필요한 행동을 캡슐화하여 인터페이스로 정의 합니다.
r ConcreteState 서브클래스들
각 서브클래스들은 Context의 상태에 따라 처리되어야 할 실제 행동을 구현합니다.
협력 방법
r 상태에 따라 다른 요청을 받으면 Context클래스는 현재의 ConcreteState개체로 전달합니다. .
r Context클래스는 실제 연산을 처리할 State개체에 자신을 매개변수로 전달합니다. 이로써 State개체는 Context클래스에 정의된 정보에 접근할 수 있게 됩니다.
r Context클래스는 사용자가 사용할 수 있는 기본 인터페이스를 제공합니다. 사용자는 상태 개체를 Context개체와 연결 시킵니다. 즉, Context클래스에 현재 상태를 정의 하는 것입니다. 이렇게 Context개체를 만들고 나면 사용자는 더는 State개체를 직접 다루지 않고 Context개체에 요청을 보내기만 하면 됩니다.
r Context클래스 또는 ConcreteState서브 클래스들은 자기 다음의 상태가 무엇이고, 어떤 환경에서 다음 상태로 가는지 결정할 수 있습니다. 즉, 상태는 상태 전이의 규칙이 있으므로 각각 한 상태에서 다른 상태로 전이하는 규칙을 알아야 합니다.
결과
r 상태에 따른 행동을 국소화하며, 서로 다른 상태에 대한 행동을 별도의 개체로 관리합니다.
r 상태 전이를 명확하게 만듭니다.
r 상태 개체는 공유될 수 있습니다.
실습 예제
예제 설명
시스템은 파일을 파일저장소에 입력하고, 파일저장소에 입력된 파일을 원격지에 전송하고, 전송이 끝난 파일을 파일저장소에서 제거하는 작업을 수행한다. 이때 파일 저장 소는 Free, Ready, Transfer상태를 가지게 된다.
①Free상태 에서는 파일저장소가 파일을 입력 받을 수 있는지 등을 점검하게 된다.
②점검이 끝나면 파일을 입력 받을 준비가 되는 Ready상태가 된다.
③파일저장소에 입력하고 나면, 파일 전송이 가능한 Transfer상태가 된다.
④전송하고 나면 전송이 끝난 파일은 파일저장소에서 제가가 된다.
⑤파일저장소는 처음 상태인 Free가 된다.
예제 구현
서브클래스들이 상속 받을 State를 다음과 같이 만든다.
모두 가상 메서드로 이루어지고 각 가상 메서드는 에러처리만 하도록 만든다. 후손개체의 메서드가 사용되지 않고, 조상 개체의 메서드가 사용되면 자동으로 에러가 나도록 만든다.
public class State
{
public virtual bool CheckFile(FileManager fileMgr)
{
Console.WriteLine("<오류발생>CheckFile-상태에러");
return false;
}
public virtual bool GetFile(FileManager fileMgr)
{
Console.WriteLine("<오류발생>GetFile-상태에러");
return false;
}
public virtual void TransterFile(FileManager fileMgr)
{
Console.WriteLine("<오류발생>TransterFile-상태에러");
}
} |
싱글톤패턴 적용하여 시스템에 하나의 개체만 존재하도록 하였고, 생성자는 파일관리(FileManager Class)개체를 입력 받도록 만들어주어, 나중에 상태개체를 변경해야 할 때 사용한다.
파일 저장소를 점검하고, 에러가 없으면 파일저장소의 상태를 변경한다.
class FreeState : State
{
private static FreeState this_object;
public static FreeState get_object
{
get
{
if (this_object == null)
{
this_object = new FreeState();
}
return this_object;
}
}
public override bool CheckFile(FileManager fileMgr)
{
if (!checkPool())
{
Console.WriteLine("파일을 저장 할 수 없습니다.");
return false;
}
Console.WriteLine("사용될 타일들 검사 중...");
Console.WriteLine("모두 OK");
fileMgr.changeState(ReadyState.get_object); ※상태를 변경함.
return true;
}
private bool checkPool()
{
Console.WriteLine("파일 저장소 검사중...");
Console.WriteLine("이상 무!"); ※파일 저장소의 상태는 에러가 없다 가정함.
return true;
}
} |
※Ready상태와 Transfer상태를 담당하는 ReadyState Class와 TransferState Class 도 유사한 방법으로 만들어 준다.
파일관리를 담당 하게 될 FileManager 클래스를 보도록 하자. FileManager Class는 내부에 State 개체를 가지게 되는데, 이 개체는 수시로 각 상태를 담당하는 개체로 형 변환을 하게 된다.
public class FileManager
{
private State state;
public FileManager()
{
changeState(FreeState.get_object);
}
public void changeState(State state)
{
this.state = state;
}
public void checkFile()
{
if (!state.CheckFile(this))
{
Console.WriteLine("<오류통보> CheckFile");
}
}
public void getFile()
{
if(!state.GetFile(this))
{
Console.WriteLine("<오류통보> GetFile");
}
}
public void transferFile()
{
state.TransterFile(this);
}
} |
마지막으로 파일관리 개체를 활용하는 모듈을 작성한다. 이때 모듈은 파일관리 개체의 상태에 전혀 신경을 쓰지 않아도 된다. 그냥 메서드를 호출하여 사용하기만 하면 된다.
class Program
{
static void Main(string[] args)
{
FileManager fileMgr = new FileManager();
fileMgr.checkFile();
fileMgr.getFile();
fileMgr.transferFile();
}
} |
예제 결과
|
|