U E D R , A S I H C RSS

Gof/Facade (rev. 1.1)

Gof/Facade

From GoF.


1. FACADE

1.1. Intent

서브시스템의 인터페이스집합에 일관된 인터페이스를 제공한다. Facade는 고급레벨의 인터페이스를 정의함으로서 서브시스템을 더 사용하기 쉽게 해준다.

1.2. Motivation

서브시스템을 구축하는 것은 복잡함을 줄이는데 도움을 준다. 일반적인 디자인의 목적은 각 서브시스템간의 통신과 의존성을 최소화시키는 것이다. 이 목적을 성취하기 위한 한가지 방법으로는 단일하고 단순한 인터페이스를 제공하는 facade object를 도입하는 것이다.


예를 들기 위해, 어플리케이션에게 컴파일러 서브시스템을 제공해주는 프로그래밍 환경이 있다고 하자. 이 서브시스템은 컴파일러를 구현하는 Scanner, Parser, ProgramNode, BytecodeStream, 그리고 ProgramNodeBuilder 클래스를 포함하고 있다. 몇몇 특수화된 어플리케이션은 이러한 클래스들을 직접적으로 접근할 필요가 있을 것이다. 하지만, 대부분의 컴파일러 시스템을 이용하는 클라이언트들은 일반적으로 구문분석(Parsing)이나 코드 변환 (Code generation) 의 세부적인 부분에 대해 신경쓸 필요가 없다.(그들은 단지 약간의 코드를 컴파일하기 원할뿐이지 다른 강력한 기능을 알 필요가 없다.) 그러한 클라이언트들에게는 컴파일러 서브시스템의 강력하지만 저급레벨인 인터페이스는 단지 그들의 작업을 복잡하게 만들 뿐이다.

이러한 클래스들로부터 클라이언트들을 보호할 수 있는 고급레벨의 인터페이스를 제공하기 위해 컴파일러 서브시스템은 facade 로서 Compiler class를 포함한다. 이러한 클래스는 컴파일러의 각 기능성들에 대한 단일한 인터페이스를 정의한다. Compiler class는 facade (원래의 단어 뜻은 건물의 전면. 외관, 겉보기..) 로서 작용한다. Compiler class는 클라이언트들에게 컴파일러 서브시스템에 대한 단일하고 단순한 인터페이스를 제공한다. Compiler class는 컴파일러의 각 기능들을 구현한 클래스들을 완벽하게 은폐시키지 않고, 하나의 클래스에 포함시켜서 붙인다. 컴파일러 facade 는저급레벨의 기능들의 은폐없이 대부분의 프로그래머들에게 편리성을 제공한다.


1.3. Applicabilty

이럴때 Facade Pattern을 사용하라.
  • 복잡한 서브 시스템에 대해 단순한 인터페이스를 제공하기 원할때. 서브시스템은 종종 시스템들이 발전되어나가면서 더욱 복잡성을 띄게 된다. 대부분의 패턴들은 패턴이 적용된 결과로 많고 작은 클래스들이 되게 한다. 패턴의 적용은 서브시스템들이 더 재사용가능하고 커스터마이즈하기 쉽게 하지만, 커스터마이즈할 필요가 없는 클라이언트들이 사용하기 어렵게 만든다. Facade는 서브시스템에 대한 단순하고 기본적인 시각을 제공한다. 이러한 시각은 대부분의 클라이언트들에게 충분하다. 커스터마이즈가 필요한 클라이언트들에게만이 facade를 넘어서 볼 필요가 있는 것이다.
  • 클라이언트들과 추상 클래스들의 구현 사이에는 많은 의존성이 있다. 클라이언트와 서브시스템 사이를 분리시키기 위해 facade를 도입하라. 그러함으로서 서브클래스의 독립성과 Portability를 증진시킨다.
  • 서브시스템에 계층을 두고 싶을 때. 각 서브시스템 레벨의 entry point를 정의하기 위해 facade를 사용하라. 만일 각 서브시스템들이 서로 의존적이라면 서브시스템들간의 대화를 각 시스템간의 facade로 단일화 시킴으로서 그 의존성을 단순화시킬 수 있다.



1.4. Participant

Facade (Compiler)
- 각 서브시스템 클래스는 각 요청에 대한 책임이 있다.
- 클라이언트의 요청을 적합한 서브시스템 객체에게 위임한다.
subsystem classes (Scanner, Parser, ProgramNode, etc.)
- 서브시스템 기능들이 구현되어있다.
- Facade 객체에 의해 정의된 작업을 처리한다.
- facade 에 대한 정보가 필요없다. facade object에 대한 reference를 가지고 있을 필요가 없다.

1.5. Collaborations

  • 클라이언트는 Facade에게 요청을 보냄으로서 서브시스템과 대화한다. Facade 객체는 클라이언트의 요청을 적합한 서브시스템 객체에게 넘긴다. 비록 서브시스템 객체가 실제 작업을 수행하지만, facade 는 facade 의 인퍼페이스를 서브시스템의 인터페이스로 번역하기 위한 고유의 작업을 해야 할 것이다.
    facade 를 사용하는 클라이언트는 직접 서브시스템 객체에 접근할 필요가 없다.

1.6. Consequences

Facade Pattern은 다음과 같은 이익을 제공해준다.
  1. 서브시스템 컴포넌트로부터 클라이언트들을 보호한다. 그러함으로서 클라이언트가 다루는 객체의 수를 줄이고, 서브시스템을 이용하기 쉽게 해준다.
  2. 서브시스템과 클라이언트 간의 연결관계를 약하게 해준다. 종종 서브시스템 컴포넌트는 클라이언트와 강한 연결관계를 가지기도 한다. 약한 연결관계는 클라이언트에게 영향을 미치지 않으면서 서브시스템의 컴포넌트들을 다양하게 만들어준다. Facade 는 시스템과 객체간의 의존성을 계층화 하는데 도움을 준다. Facade는 복잡함이나 순환의존성을 없애준다. 이것은 클클라이언트와 서브시스템이 비의존적으로 구현되었을때의 가장 중요한 결과일 것이다.
  3. 그러하면서도 어플리케이션은 여전히 서스시스템 클래스들을 사용할 방법을 제공한다. 사용의 편리성과 일반성을 선택할 수 있다.

1.7. Implementation

facade를 구현할 때 다음과 같은 issue를 생각하라.
  1. 클라이언트-서브시스템 연결관계를 줄여라.
클라이언트와 서브시스템간의 연결관계는 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의 스트림을 만든다.

~cpp 
class Scanner {
public:
	Scanner (istream&);
	virtual ~Scanner ();

	virtual Token& Scan ();
private:
	istream& _inputStream;
};


Parser 클래스는 Scanner의 token로 parse tree를 구축하기 위해 ProgramNodeBuilder 를 사용한다.

~cpp 
class Parser {
public:
	Parser ();
	virtual ~Parser ();

	virtual void Parse (Scanner&, ProgramNodeBuilder &);
};

Parser는 점진적으로 parse tree를 만들기 위해 ProgramNodeBuilder 를 호출한다. 이 클래스들은 Builder pattern에 따라 상호작용한다.

~cpp 
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;
};

parser tree는 StatementNode, ExpressionNode와 같은 ProgramNode의 subclass들의 인스턴스들로 이루어진다. ProgramNode 계층 구조는 Composite Pattern의 예이다. ProgramNode는 program node 와 program node의 children을 조작하기 위한 인터페이스를 정의한다.

~cpp 
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 ();
};

Traverse operaton은 CodeGenerator 객체를 인자로 취한다. ProgramNode subclass들은 BytecodeStream에 있는 Bytecode객체들을 machine code로 변환하기 위해 CodeGenerator 객체를 사용한다. CodeGenerator 클래는 visitor이다. (VisitorPattern을 참조하라)

~cpp 
class CodeGenerator {
public:
	virtual void Visit (StatementNode*);
	virtual void Visit (ExpressionNode*);
	// ...
protected:
	CodeGenerator (BytecodeStream&);
protected:
	BytecodeStream& _output;
};

CodeGenerator 는 subclass를 가진다. 예를들어 StackMachineCodeGenerator sk RISCCodeGenerator 등. 각각의 다른 하드웨어 아키텍처에 대한 machine code로 변환하는 subclass를 가질 수 있다.

ProgramNode의 각 subclass들은 ProgramNode의 child인 ProgramNode 객체를 호출하기 위해 Traverse operation을 구현한다. 매번 각 child는 children에게 같은 일을 재귀적으로 수행한다. 예를 들어, ExpressionNode는 Traverse를 다음과 같이 정의한다.
~cpp 
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에 대한 코드변환기능에 대한 단순한 인터페이스를 제공한다.

~cpp 
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의 의의를 떨어뜨린다.

1.8. Known Uses

Sample Code의 compiler 의 예는 ObjectWorksSmalltalk compiler system에서 영감을 얻은 것이다.Par90
ET++ application framework WGM88 에서, application은 run-time 상에서 application의 객체들을 살필 수 수 있는 built-in browsing tools를 가지고 있다.이러한 browsing tools는 "ProgrammingEnvironment'라 불리는 facade class를 가진 구분된 서브시스템에 구현되어있다. 이 facade는 browser에 접근 하기 위한 InspectObjectInspectClass같은 operation을 정의한다.
ET++ application은 또한 built-in browsing support를 없앨수도 있다. 이러한 경우 ProgrammingEnvironment는 이 요청에 대해 null-operation으로서 구현한다. 그러한 null-operation는 아무 일도 하지 않는다. 단지 ETProgrammingEnvironment subclass는 각각 대응하는 browser에 표시해주는 operation을 가지고 이러한 요청을 구현한다. application은 browsing environment가 존재하던지 그렇지 않던지에 대한 정보를 가지고 있지 않다. application 과 browsing 서브시스템 사이에는 추상적인 결합관계가 있다.

Choices operating system CIRM93 은 많은 framework를 하나로 합치기 위해 facade를 사용한다. Choices에서의 key가 되는 추상객체들은 process와 storge, 그리고 adress spaces 이다. 이러한 각 추상객체들에는 각각에 대응되는 서브시스템이 있으며, framework로서 구현된다. 이 framework는 다양한 하드웨어 플랫폼에 대해 Choices에 대한 porting을 지원한다. 이 두 서브시스템은 '대표자'를 가진다. (즉, facade) 이 대표자들은 FileSystemInterface (storage) 와 Domain (address spaces)이다.


예를 들어, 가상 메모리 framework는 Domain을 facade로서 가진다. Domain은 address space를 나타낸다. Domain은 virtual addresses 와 메모리 객체, 화일, 저장소의 offset에 매핑하는 기능을 제공한다. Domain의 main operation은 특정 주소에 대해 메모리 객체를 추가하거나, 삭제하너가 page fault를 다루는 기능을 제공한다.

앞의 다이어그램이 보여주듯, 가상 메모리 서브시스템은 내부적으로 다음과 같은 컴포넌트를 이용한다.

  • MemoryObject 는 데이터 저장소를 나타낸다.
  • MemoryObjectCache는 물리적 메모리에서의 MemoryObject의 데이터를 캐쉬한다.
    MemoryObjectCache는 실제로는 캐쉬 정책을 지역화시키는 Strategy Pattern이다.
  • AddressTranslation 은 address translation hardware 를 캡슐화한다.

    RepairFault 명령은 page fault 인터럽트가 일어날때 호출된다. Domain은 fault 를 야기시킨 주소의 메모리객체를 찾은뒤 RepairFault에 메모리객체과 관계된 캐쉬를 위임한다. Domain들은 컴포넌트를 교체함으로서 커스터마이즈될 수 있다.

1.9. Related Patterns

AbstactFactory 는 Facade구현시 서브시스템 독립적인 방법으로 서브시스템 객체를 만들 수 있는 인터페이스를 제공하기 위해 사용한다. Abstract Factory는 또한 플랫폼 비독립적 클래스를 감추기 위해 Facade의 대안으로서 사용할 수 있다.

Mediator 는 존재하는 class들의 기능들을 추상화시킨다는 점에서 Facade와 비슷하다. 하지만 Mediator의 목적은 정해지지 않은 동료클래스간의 통신을 추상화시키고, 해당 동료클래스군 어디에도 포함되지 않는 기능들을 중앙으로 모은다. Mediator의 동료클래스들은 Mediator에 대한 정보를 가지며 서로 직접적으로 통신하는 대신 mediator를 통해 통신한다. 대조적으로 facade는 단지 서브시스템들을 사용하기 편하게 하기 위해서 서브시스템들의 인터페이스를 추상화시킬 뿐이다. facade는 새로운 기능을 새로 정의하지 않으며, 서브시스템 클래스는 facade에 대한 정보를 가질 필요가 없다.

보통 facade는 단일 오브젝트로 요구된다. 그래서Facade 객체는 종종 SingletonPattern으로 구현된다.

Valid XHTML 1.0! Valid CSS! powered by MoniWiki
last modified 2021-02-07 05:23:18
Processing time 0.0540 sec