U E D R , A S I H C RSS

Gof/Composite

1. Composite

1.1. Intent

부분-전체 계층구조를 표현하기 위해 객체들을 트리구조로 구성한다. Composite 는 클라이언트로 하여금 각각의 객체들과 객체의 묶음을 동일한 방법으로 처리하게 해준다.

1.2. Motivation

드로우 에디터나 회로설계 시스템과 같은 그래픽 어플리케이션은 단순한 컴포넌트들의 차원을 넘어서 복잡한 도표들을 만들어내는데 이용된다. 사용자는 더 큰 컴포넌트들을 형성하기 위해 컴포넌트들을 그룹화할 수 있고, 더 큰 컴포넌트들을 형성하기 위해 또 그룹화 할 수 있다. 단순한 구현방법으로는 Text 나 Line 같은 그래픽의 기본요소들에 대한 클래스들을 정의한 뒤, 이러한 기본요소들에 대해 컨테이너 역할을 하는 다른 클래스에 추가하는 방법이 있다.

하지만, 이러한 접근방법에는 문제점이 있다. 비록 대부분의 시간동안 사용자가 개개의 객체들을 동일하게 취급한다 하더라도, 이러한 클래스들을 이용하는 코드는 반드시 기본객체와 컨테이너 객체를 다르게 취급하여 코딩해야 한다는 점이다. 이러한 객체들의 구별은 어플리케이션을 복잡하게 만든다. CompositePattern은 객체들에 대한 재귀적 조합 방법을 서술함으로서, 클라이언트들로 하여금 이러한 구분을 할 필요가 없도록 해준다.


CompositePattern의 핵심은 기본요소들과 기본요소들의 컨테이너를 둘 다 표현하는 추상 클래스에 있다. 그래픽 시스템에서 여기 Graphic class를 예로 들 수 있겠다. Graphic 은 Draw 와 같은 그래픽 객체들을 구체화하는 명령들을 선언한다. 또한 Graphic 은 Graphic 의 자식클래스들 (tree 구조에서의 parent-child 관계)에 대해 접근하고 관리하는 명령들과 같은 모든 composite 객체들이 공유하는 명령어들을 선언한다.

Line, Rectangle, Text 와 같은 서브 클래스들은 (앞의 class diagram 참조) 기본 그래픽 객체들을 정의한다. 이러한 클래스들은 각각 선이나 사각형, 텍스트를 그리는 'Draw' operation을 구현한다. 기본적인 그래픽 구성요소들은 자식요소들을 가지지 않으므로, 이 서브 클래스들은 자식요소과 관련된 명령들을 구현하지 않는다.

Picture 클래스는 Graphic 객체에 대한 포함관계를 정의한다. Picture 는 Picture의 children의 Draw 메소드를 호출하게끔 Draw 메소드를 구현한다. 그리고 Picture는 child와 관련한 명령어들을 알맞게 구현한다. Picture 인터페이스는 Graphic의 인터페이스를 형성하므로, Picture 객체는 다른 Pricture들을 재귀적으로 조합할 수 있다.

다음 다이어그램은 재귀적으로 Graphic 객체들이 조합된 일반적인 composite 객체의 구조를 보여준다.


1.3. Applicability

다음과 같은 경우에 CompositePattern 을 이용할 수 있다.
  • 객체들의 부분-전체 계층 구조를 표현하고 싶을때
  • 클라이언트들이 개개별 객체들과 객체들의 조합들의 차이점에 신경쓰지 않도록 하고 싶을 경우. Composite를 이용함으로써, 클라이언트들은 composite 구조의 모든 객체들을 동등하게 취급할 것이다.

1.4. Structure


A typical Composite object structure might look like this:
일반적인 Composite 객체구조는 다음과 같은 모습을 띈다.

1.5. Participants

  • Component (Graphic)
    • composition 에의 객체들에 대한 인터페이스를 선언한다.
    • 모든 클래스들에의 기본적인 인터페이스에 대한 기본 행위를 구현한다.
    • 자식 컴포넌트들을 접근하고 관리하기 위한 인터페이스를 선언한다.
    • (optional) 재귀 구조에서의 해당 컴포넌트의 부모 클래스로 접근하기 위한 인터페이스를 정의하고, 적절하게 구현한다.
  • Leaf (Rectangle, Line, Text, etc.)
    • composition의 leaf 객체를 표현한다. leaf 는 children를 가지지 않는다.
    • composition 의 기본 객체들의 행위를 정의한다.
  • Composite (Picture)
    • children을 가지는 컴포넌트들을 위한 행위를 정의한다.
    • 자식 컴포넌트들을 저장한다.
    • Component 인터페이스중 자식컴포넌트 관련 명령들을 구현한다.
  • Client
    • Component의 인터페이스를 통해 composition의 객체들을 조작한다.

1.6. Collaborations

  • 클라이언트들은 Component 클래스의 인터페이스를 이용, composite 구조의 객체들과 상호작용을 한다. 만일 상호작용하는 객체가 Leaf인 경우, 해당 요청은 직접적으로 처리된다. 만일 상호작용하는 객체가 Composite인 경우, Composite는 해당 요청을 자식 컴포넌트들에게 전달하는데, 자식 컴포넌트들에게 해당 요청을 전달하기 전 또는 후에 추가적인 명령들을 수행할 수 있다.

1.7. Consequences

CompositePattern
  • 기본 객체들과 복합 객체들로 구성된 클래스 계층 구조를 정의한다. (상속관계가 아님. 여기서는 일종의 data-structure의 관점) 기본 객체들은 더 복잡한 객체들을 구성할 수 있고, 계속적이고 재귀적으로 조합될 수 있다. 클라이언트 코드가 기본 객체를 원할때 어디서든지 복합 객체를 취할 수 있다.
  • 클라이언트를 단순하게 만든다. 클라이언트는 각각의 객체와 복합 구조체를 동등하게 취급할 수 있다. 클라이언트는 그들이 다루려는 객체가 Composite 인지 Leaf 인지 알 필요가 없다. 이러함은 composition 을 정의하는 클래스들에 대해 상관없이 함수들을 단순하게 해준다.
  • 새로운 종류의 컴포넌트들을 추가하기 쉽게 해준다. 새로 정의된 Composite 나 Leaf 의 서브클래스들은 자동적으로 현재의 구조들과 클라이언트 코드들과 작용한다. 클라이언트 코드들은 새로운 Component 클래스들에 대해서 수정될 필요가 없다.
  • 디자인은 좀 더 일반화시켜준다. 새로운 컴포넌트를 추가하기 쉽다는 점이 단점으로 작용하는 경우는 composite의 컴포턴트들로 하여금 제한을 두기 힘들다는 점이 있다. 때때로 특정 컴포넌트들만을 가지는 composite를 원할 때가 있을 것이다. Composite 인 경우, 이러한 제한을 강제도 두기 위해 type system에 의존할 수 없게 된다. 그 대신 실시간 체크를 이용해야 할 것이다.

1.8. Implementation

CompositePattern을 구현할 때 고려해야 할 여러가지 사항들이 있다.
  1. 부모의 참조자를 명시하라.
  2. 컴포넌트들의 공유
  3. Component 인터페이스의 최대화
  4. Child 관리 명령어들의 선언
  5. Component는 컴포넌트들의 리스트구조를 구현해야 하는가?
  6. Child ordering
  7. 성능향상을 위한 caching
  8. 누가 컴포넌트들을 삭제해야 하는가?
  9. 컴포넌트들을 저장하기 위한 가장 좋은 자료구조는 무엇인가?

1.9. Sample Code

computer 와 스테레오 컴포넌트들과 같은 장치들 (Equipment) 는 보통 격납 계층의 부분-전체 식으로 구성된다. 예를 들어 섀시 (chassis) 는 드라이브들(하드디스크 드라이브, 플로피 디스크 드라이브 등) 과 평판들 (컴퓨터 케이스의 넓은 판들) 을 포함하고, 버스는 카드들을 포함할 수 있고, 캐비넷은 섀시와 버스 등등을 포함할 수 있다. 이러한 구조는 자연스럽게 CompositePattern으로 모델링될 수 있다.

Equipment 클래스는 부분-전체 계층구조의 모든 equipment들을 위한 인터페이스를 정의한다.
~cpp 
class Equipment {
public:
	virtual ~Equipment ();

	const char* Name () { return _name; }

	virtual Watt Power ();
	virtual Currency NetPrice ();
	virtual Currency DiscountPrice ();

	virtual void Add (Equipment* );
	virtual void Remove (Equipment* );
	virtual Iterator<Equipment*>* CreateIterator ();

protected:
	Equipment (const char*);

private:
	const char* _name;
};

Equipment 는 전원소모량 (power consumption)과 가격(cost) 등과 같은 equipment의 일부의 속성들을 리턴하는 명령들을 선언한다. 서브클래스들은 해당 장비의 구체적 종류에 따라 이 명령들을 구현한다. Equipment 는 또한 Equipment의 일부를 접근할 수 있는 Iterator 를 리턴하는 CreateIterator 명령을 선언한다. 이 명령의 기본적인 구현부는 비어있는 집합에 대한 NullIterator 를 리턴한다.

Equipment 의 서브클래스는 디스크 드라이브나 IC 회로, 스위치 등을 표현하는 Leaf 클래스를 포함할 수 있다.
~cpp 
class FloppyDisk : public Equipment {
public:
	FloppyDisk (const char*);
	virtual ~FloppyDisk ();
	virtual Watt Power ();
	virtual Currency NetPrice ();
	virtual Currency DiscountPrice ();
};

CompositeEquipment 는 다른 equipment를 포함하는 equipment의 기본클래스이다. 이는 또한 Equipment 의 서브클래스이다.

~cpp 
class CompositeEquipment : public Equipment {
public:
	virtual ~CompositeEquipment ();

	virtual Watt Power ();
	virtual Currency NetPrice ();
	virtual Currency DiscountPrice ();

	virtual void Add (Equipement* );
	virtual void Remove (Equipment* );
	virtual Iterator<Equipment*>* CreateIterator ();

protected:
	CompositeEquipment (const char*);
private:
	List<Equipment*>_equipment;
};

CompositeEquipment 는 sub-equipment 에 접근하고 관리하기 위한 명령들을 정의한다. 이 명령들인 Add 와 Remove는 _equipment 멤버에 저장된 equipment 의 리스트로부터 equipment 를 추가하거나 삭제한다. CreateIterator 명령은 이 리스트들을 탐색할 수 있는 iterator(구체적으로 ListIterator의 인스턴스) 를 리턴한다.

NetPrice 의 기본 구현부는 sub-equipment 의 net price의 합을 구하기 위해 CreateIterator를 이용할 것이다.
~cpp 
Currency CompositeEquipment::NetPrice () {
	Iterator<Equipment*>* i = CreateIterator ();
	Currency total = 0;

	for (i->first (); !i->IsDone (); i->Next ()) {
		total += i->CurrentItem ()->NetPrice ();
	}
	delete i;
	return total;
}

자, 우리는 컴퓨터 섀시를 Chassis 라 불리는 CompositeEquipment의 서브클래스로서 표현할 수 있다. Chassis는 CompositeEquipment로부터 자식-관련 명령어들을 상속받는다.
~cpp 
class Chassis : public CompositeEquipment {
public:
	Chassis (const char*);
	virtual ~Chassis ();

	virtual Watt Power ();
	virtual Currency NetPrice ();
	virtual Currency DiscountPrice ();
};

우리는 간단한 방법으로 Cabinet 나 Bus 와 같은 다른 equipment 컨테이너를 정의할 수 있다. 이로서 우리가 개인용 컴퓨터에 equipment들을 조립하기 위해 (꽤 간단하게) 필요로 하는 모든 것들이 주어졌다.
~cpp 
Cabinet* cabinet = new Cabinet ("PC Cabinet");
Chassis* chassis = new Chassis ("PC Chassis");

cabinet->Add (chassis);

Bus* bus = new Bus ("MCA Bus");
bus->Add (new Card("16Mbs Token Ring"));

chassis->Add (bus);
chassis->Add (new FloppyDisk ("3.5in Floppy"));

cout << "The net price is " << chassis->NetPrice () << endl;

1.10. Known Uses

CompositePattern의 예는 거의 모든 객체지향 시스템에서 찾을 수 있다. Smalltalk 의 Model/View/Container KP88 의 original View 클래스는 Composite이며, ET++ (VObjects WGM88) 이나 InterViews (Styles LCI+92, Graphics VL88, Glyphs CL90)등 거의 대부분의 유저 인터페이스 툴킷과 프레임워크가 해당 과정을 따른다. Model/View/Controller 의 original View에서 주목할만한 점은 subview 의 집합을 가진다는 것이다. 다시 말하면, View는 Component class 이자 Composite class 이다. Smalltalk-80 의 Release 4.0 은 View 와 CompositeView 의 서브클래스를 가지는 VisualComponent 클래스로 Model/View/Controller 를 변경했다.

RTL Smalltalk 컴파일러 프레임워크 JML92CompositePattern을 널리 사용한다. RTLExpression 은 parse tree를 위한 Component 클래스이다. RTLExpressionBinaryExpression 과 같은 서브클래스를 가지는데, 이는 RTLExpression 객체들을 자식으로 포함한다. 이 클래스들은 parse tree를 위해 composite 구조를 정의한다. RegisterTransfer 는 프로그램의 Single Static Assignment(SSA) 형태의 중간물을 위한 Component 클래스이다. RegisterTransfer 의 Leaf 서브클래스들은 다음과 같은 다른 형태의 static assignment 를 정의한다.
  • 두개의 레지스터로 명령을 수행하고 세번째 레지스터로 결과값을 할당하는 기본적인 assignment
  • source register 를 가지지만, destination register 를 가지지 않는, register가 해당 루틴이 리턴 된 뒤에 이용되는 assignment
  • destination register 를 가지지만, source register 를 가지지 않는, 해당 루틴이 시작되기 전에 register 가 assign되는 assignment

Another subclass, RegisterTransferSet, is a Composite class for representing assignments that change several registers at once.
또 다른 서브클래스로서 RegisterTransferSet이 있다. RegisterTransferSet 는 한번에 여러 register를 변경하는 assignment를 표현하기 위한 Composite 클래스이다.

Another example of this pattern occurs in the financial domain, where a portfolio aggregates individual assets. You can support complex aggregations of assets by implementing a portfolio as a Composite that conforms to the interface of an individual asset BE93.

CompositePattern의 또다른 예는 각각의 자산들을 포함하는 portfolio인 financial domain 에서 나타난다. portfolio 를 각각의 asset 의 인터페이스를 구성하는 Composite 로 구현함으로써 복잡한 asset의 포함관계를 지원할 수 있다.

CommandPatternMacroCommand Composite 클래스로 Command 객체들이 조합되고 나열하는 방법에 대해 서술한다.

1.11. Related Patterns

  • 종종 컴포넌트-부모 연결은 ChainOfResponsibilityPattern에 이용된다.
  • DecoratorPattern 은 종종 Composite와 함께 이용된다. descorator 와 composite 가 함께 이용될때, 그것들은 보통 공통된 부모 클래스를 가질 것이다. 그러한 경우 decorator는 Add, Remove, GetChild 와 같은 Compoent 의 인터페이스를 지원해야 한다.
  • FlyweightPattern lets you share components, but they can no longer refer to their parents.
  • FlyweightPattern 은 컴포넌트들을 공유할 수 있도록 해주지만, 그들의 부모객체를 참조할 수 없다.
  • IteratorPattern 은 composite들을 탐색할 때 이용될 수 있다.
  • VisitorPattern은 명령들과 Composite 와 Leaf 클래스 사이를 가로질러 분포될 수 있는 행위들을 지역화한다.


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