facade를 구현할 때 다음과 같은 issue를 생각하라.
- 클라이언트-서브시스템 연결관계를 줄여라.
클라이언트와 서브시스템간의 연결관계는 Facade를 추상클래스로 만듬으로서 줄일 수 있다.
그러면 클라이언트는 추상 Facade class의 인터페이스를 통해 서브시스템과 대화할 수 있다. 이러한 추상클래스와의 연결은 클라이언트가 사용할 서브시스템의 구현을 알아야 하는 필요성을 없애준다.
서브클래싱의 대체는 다른 서브시스템 객체를 가진 Facade 객체로 설정하는 것이다. facade를 커스터마이즈하려면 단순히 서브시스템 객체를 다른 객체로 교환한다.
2. public vs private 서브시스템 클래스.
서브시스템은 인터페이스를 가진다는 점과 무엇인가를 (클래스는 state와 operation을 캡슐화하는 반면, 서브시스템은 classes를 캡슐화한다.) 캡슐화한다는 점에서 class 와 비슷하다. class 에서 public 과 private interface를 생각하듯이 우리는 서브시스템에서 public 과 private interface 에 대해 생각할 수 있다.
서브시스템으로의 public interface는 모든 클라이언트들이 접속가능한 클래스들로 구성되며. 이때 서브시스템으로의 private interface는 단지 서브시스템의 확장자들을 위한 인터페이스이다. 따라서 facade class는 public interface의 일부이다. 하지만, 유일한 일부인 것은 아니다. 다른 서브시스템 클래스들 역시 대게 public interface이다. 예를 들자면, 컴파일러 서브시스템의 Parser class나 Scanner class들은 public interface의 일부이다.
서브시스템 클래스를 private 로 만드는 것은 유용하지만, 일부의 OOP Language가 지원한다. C++과 Smalltalk 는 전통적으로 class에 대한 namespace를 global하게 가진다. 하지만 최근에 C++ 표준회의에서 namespace가 추가됨으로서
Str94, public 서브시스템 클래스를 노출시킬 수 있게 되었다.
Str94 (충돌의 여지를 줄였다는 편이 맞을듯..)
Sample Code
자, Compiler 서브시스템에 어떻게 facade가 입혀지는지 자세히 보도록 하자.
Compiler 서브시스템은
BytecodeStream 클래스를 정의한다. 이 클래스는 Bytecode 객체의 스트림부를 구현한다. Bytecode 객체는 머신코드를 구체화하는 bytecode를 캡슐화한다. 서브시스템은 또한 Token 클래스를 정의하는데, Token 객체는 프로그램 언어내의 token들을 캡슐화한다.
Scanner 클래스는 character 스트림을 얻어서 token의 스트림을 만든다.
class Scanner {
public:
Scanner (istream&);
virtual ~Scanner ();
virtual Token& Scan ();
private:
istream& _inputStream;
};
class Parser {
public:
Parser ();
virtual ~Parser ();
virtual void Parse (Scanner&, ProgramNodeBuilder &);
};
Parser는 점진적으로 parse tree를 만들기 위해
ProgramNodeBuilder 를 호출한다. 이 클래스들은 Builder pattern에 따라 상호작용한다.
class ProgramNodeBuilder {
public:
ProgramNodeBuilder ();
virtual ProgramNode* NewVariable (
const char* variableName
) const;
virtual ProgramNode* NewAssignment (
ProgramNode* variable, ProgramNode* expression
) const;
virtual ProgramNode* NewRetrunStatement (
ProgramNode* value
) const;
virtual ProgramNode* NewCondition (
ProgramNode* condition,
ProgramNode* truePart, ProgramNode* falsePart
) const;
// ...
ProgramNode* GetRootNode ();
private:
ProgramNode* _node;
};
class ProgramNode {
public:
// program node manipulation
virtual void GetSourcePosition (int& line, int& index);
// ...
// child manipulation
virtual void Add (ProgramNode*);
virtual void Remove (ProgramNode*);
// ...
virtual void Traverse (CodeGenerator&);
protected:
ProgramNode ();
};
class CodeGenerator {
public:
virtual void Visit (StatementNode*);
virtual void Visit (ExpressionNode*);
// ...
protected:
CodeGenerator (BytecodeStream&);
protected:
BytecodeStream& _output;
};
ProgramNode의 각 subclass들은
ProgramNode의 child인
ProgramNode 객체를 호출하기 위해 Traverse operation을 구현한다. 매번 각 child는 children에게 같은 일을 재귀적으로 수행한다. 예를 들어,
ExpressionNode는 Traverse를 다음과 같이 정의한다.
void ExpressionNode::Traverse (CodeGenerator& cg) {
cg.Visit (this);
ListIterator <ProgramNode*> i (_children);
for (i.First (); !i.IsDone (); i.Next ()) {
i.CurrentItem ()->Traverse (cg);
}
}
우리가 토론해온 클래스들은 곧 Compiler 서브시스템을 이룰 것이다. 자 이제 우리는 이 모든 조각들을 함께 묶은 facade 인 Compiler 클래스를 소개할 것이다. Compiler는 소스 컴파일과 특정 machine에 대한 코드변환기능에 대한 단순한 인터페이스를 제공한다.
class Compiler {
public:
Compiler ();
virtual void Compile (istream&, BytecodeStream&);
};
void Compiler::Compile (
istream& input, BytecodeStream& output
) {
Scanner scanner (input);
ProgramNodeBuilder builder;
Parser parser;
parser.Parse (scanner, builder);
RISCCodeGenerator generator (output);
ProgramNode* parseTree = builder.GetRootNode ();
parseTree->Traverse (generator);
}
이 구현에서는 사용하려는 code-generator의 형태에 대해서 hard-codes (직접 특정형태 부분을 추상화시키지 않고 바로 입력)를 했다. 그렇게 함으로서 프로그래머는 목적이 되는 아키텍처로 구체화시키도록 요구받지 않는다. 만일 목적이 되는 아키텍처가 단 하나라면 그것은 아마 이성적인 판단일 것이다. 만일 그러한 경우가 아니라면 우리는 Compiler 의 constructor 에
CodeGenerator 를 인자로 추가하기 원할 것이다. 그러면 프로그래머는 Compiler를 instance화 할때 사용할 generator를 구체화할 수 있다. Compiler facade는 또한 Scanner나
ProgramNodeBuilder 등의 다른 협동하는 서브시스템클래스를 인자화할 수 있다. 그것은 유연성을 증가시키지만, 또한 일반적인 사용형태에 대해 인터페이스의 단순함을 제공하는 Facade pattern의 의의를 떨어뜨린다.