The Composite Pattern

종종 프로그래머들은 독립적인 객체나 객체들의 수집으로 표현될 수 있는 객체를 갖는 컴포넌트로 이루어진 시스템을 개발한다. 컴포지트 패턴은 위의 두 가지 경우를  조정하기 위하여 계획되어진다. 컴포지트 패턴은 부분-전체의 상속을 만들거나 트리로 데이터를 표현하는 걸 만들려 할 때 사용되어 질 수 있다. 요약해서, 하나의 컴포지트는 객체들의 수집이다. 트리 목록에서, 어떤 객체들은 추가적인 가지들을 가지는 노드일 수도 있고 어떤것들은 잎들일 수도 있다.

    하나의 조합에서 모든 객체들에 접근할 수 있는 인터페이스 분리가 문제이고, 노드들과 잎 사이를 구분하는 것이 문제이다.  노드들은 자식을 가질 수 있고, 자식들은 노드를 가질 수 있는 반면, 잎들은 자식을 가질 수 없고, 구현할 때는  잎들은 노드를 가지지 않도록 해야한다. 

    몇몇 사람들은 노드와 잎을 구분하는 인터페이스를 만들것을 제안하였고, 여기서 잎은 메소드들은 가질 수 있다.


public String getName();
public String getValue();

    그리고 노드는 추가적인 메소들을 가질 수 있다 :

public Enumeration elements();
public Node getChild(String nodeName);
public void add(Object obj);
public void remove(Object obj);

    이 다음 엘리먼트들이 언제 컴포지트를 생성해야하는가에 대한 프로그래밍 문제를 남긴다.그러나, 디자인 패턴은 각 엘리먼트가 컴포지트이건 원래의 엘리먼트이건 간에 같은 인터페이스를 갖도록 할 것을 제안한다. 이것은 목표를 이루기가 좀더 쉽지만, getChild() 연산이 객체가 실제적으로 잎일 때 어떻게 완성될 수 있는지에 대한 질문을 남기게 된다. 
    자바는 이것을 아주 쉽게 만들었는데, 왜냐면, 모든 노드나 잎이 자식이 어디에 저장되어 있는지에 대한 벡터의 내용의Enumeration을 반환할 수 있기 때문이다. 만약 자식이 없으면, hasMoreElements() 메소드가 바로 false를 반환한다. 그러므로, 각 엘리먼트에서 Enumeration을 얻을 수 있다면 우리는 hasMoreElements() 메소드를 체크함으로써 어떤 자식이 있는가를 결정할 수 있다.

An Implementation of Composite

작은 회사를 생각해 보자. 경영을 하는 한 사람을 가지고 시작할 수 있다. 그 사람은 물론 CEO이다. 그리고 그 사람은  마케팅과 제조를 담당하는 두사람을 고용하였다. 곧, 각각의 사람은 추가적으로 광고, 선적 기타 등등을 도울 수 있는 사람을 고용했다. 그리고 그들은 회사의 첫 번째 부회장이 되었다. 회사가 계속해서 성장함에 따라, 우리가 아래 그림에서 볼 수 있는 것처럼 구조화 될 것이다:

##########0*

    이제, 그 회사가 성공해서, 그 회사의 각각의 사원은 봉급을 받고, 그 회사의 고용비용을 물어 볼 수 있다. 우리는 모든 사원에 대한 봉급으로서 그 비용을 정한다. 여기에 컴포지트에 대한 이상적인 예가 있다.
  • 각 개인의 고용비용은 각 개인의 봉급이다.
  • 한 부서의 고용비용은 그 부서의 책임자와 그들의 부하직원의  봉급의 합이다.
    우리는 고용인이 부하직원이 있든 없든 간에 정확한 전체의 봉급을 계산하는 하나의 인터페이스를 만들고자 한다.  

public float getSalaries();
여기에서, 우리는 표준의 같은 인터페이스를 갖는 모든 컴포지트들의 생각이 아마 원시적이라는 것을 깨달았을 것이다. 우리는 실제로 개발하고 있는 모든 클래스의 종류와 관련된 public 메소드들을 선호할 것이다. getValue()와 같은 일반적인 메소드를 갖는 것보다는 우리는 getSalaries()를 사용할 것이다. 

The Employee Class

우리의 Employee 클래스는 각 고용인의 이름과 봉급을 저장할 것이고, 필요에 따라 그들을 부를 수 있게 할 것이다. 

public class Employee {
	String name;
	float salary;
	Vector subordinates;
	
	//-----------------------
	
	public Employee(String _name, float _salary) {
		name = _name;
		salary = _salary;
		subordinates = new Vector();
	}
	
	//-------------------------
	
	public float getSalary() {
		return salary;
	}
	
	//------------------------
	
	public String getName() {
		return name;
	}

    클래스가 인스턴스가 될 때 subordinates라 불리는 벡터가 생성된다는 것을 주목해야 한다. 그리고 나서, 만약 employee가 하위를 갖는 다면 우리는 자동으로 add 메소드를 이용하여 벡터에 추가할 수 있고  remove 메소드를 이용하여 벡터에서 제거할 수 있을 것이다.  

public void add(Employee e) {
	subordinates.addElement(e);
}

//-----------------------------

public void remove(Employee e) {
	subordinates.removeElement(e);
}

    만약 우리가 주어진 상위의 employee들의 리스트를 얻고자 한다면 subordinates 벡터로부터 직접적으로 그것들의 Enumeration을 얻을 수 있다. 

public Enumeration elements() {
	return subordinates.elements();
}

그 클래스의 중요한 부분은 어떻게 employee 와 그의 subordinates에 대하여 봉급의 총합을 반환하는 가이다. 

public float getSalaries() {
	float sum=salary;	//this one's salary
	//add in subordinates salaries
	for(int i=0; i < subordinates.size(); i++) {
		sum += ((Employee)subordinates.elementAt(i)).getSalaries();
	}
	
	return sum;
}

이 메소드는 현재 Employee의 봉급을 가지고 출발하고 나서 각 subordinate에 대하여 getSalaries() 메소드를 호출한다는 것을 주목해야 한다. 

Building the Employee Tree

우리는 CEO Employee를 생성하여 시작하고 그의 suborinates를 추가한다.

boss = new Employee("CEO", 200000);
boss.add(marketVP = new Employee("Marketing VP", 100000));
boss.add(prodVP = new Employee("Production VP", 100000));

marketVP.add(salesMgr = new Employee("Sales Mgr", 50000));
marketVP.add(advMgr = new Employee("Advt Mgr", 50000));

for (int i=0; i<5; i++) 
   salesMgr .add(new Employee("Sales "+new Integer(i).toString(), 30000.0F +(float)(Math.random()-0.5)*10000));
advMgr.add(new Employee("Secy", 20000));

prodVP.add(prodMgr = new Employee("Prod Mgr", 40000));
prodVP.add(shipMgr = new Employee("Ship Mgr", 35000));
for (int i = 0; i < 4; i++)
  prodMgr.add( new Employee("Manuf "+new Integer(i).toString(), 25000.0F +(float)(Math.random()-0.5)*5000));
for (int i = 0; i < 3; i++)
  shipMgr.add( new Employee("ShipClrk "+new Integer(i).toString(), 20000.0F +(float)(Math.random()-0.5)*5000));

    일단 컴포지트 구소작 생성되면, 우리는 상위 노드에서 부터 시작하고  접근할 수 있는 각 노드의 잎이 있을 때까지 재귀적으로 addNode()라 불리는 메소드를 호출하여 JTree를 시각적으로 보여줄 수 있다.

private void addNodes(DefaultMutableTreeNode pnode, Employee emp) {
	DefaultMutableTreeNode node;                                
	Enumeration e = emp.elements();                                  
  	while(e.hasMoreElements()) {
		Employee newEmp = (Employee)e.nextElement();               
		node = new DefaultMutableTreeNode(newEmp.getName());       
		pnode.add(node);                                           
		addNodes(node, newEmp);                                    
	}                                                          
}      

아래 그림은 최종적인 프로그램이다:

        ##########1*

    이 구현에서, 비용(전체 봉급 합)은 아래의 버튼에 보여지게 된다. 이 간단한 계산은  메소드가 employee의 모든 suborinates를 얻기 위해서 getChild()라 불리는 재귀적으로 부른다.

public void valueChanged(TreeSelectionEvent evt) {
	TreePath path = evt.getPath();
	String selectedTerm = path.getLastPathComponent().toString();
	
	Employee emp = boss.getChild(selectedTerm);
	if(emp != null)
		cost.setText(new Float(emp.getSalaries()).toString());
}

Restrictions on Employee Classes

어떤 employee나 job positions 은 suborinates를 갖지 않도록 설계되어질 수 있다. 집적된 workers 나 salesmen은 승진을 할수도 있는데, 잎의 위치는 suborinates가 추가될 수 없다. 이런 경우에 반영구적인 잎 위치를 정할 수 있도록 Employee 클래스를 디자인 하고자 할 수 있다. 이것을 하기 위한 하나의 방법은 subordinates가 추가되는 것을 허용하기 전에 검사되는 변수를 정하는 것이다. 만약 직위가 잎 위치라면 그 메소드는 false를 반환하거나 예외를 발생시킬 것이다. 

public void setLeaf(boolean b) {
	isLeaf = b;    //if true, do not allow children
}
//--------------------------------------
public boolean add(Employee e) {
   	if (! isLeaf) 
      		subordinates.addElement(e);   
   	return isLeaf;    //false if unsuccessful
}   

Consequences of the Composite Pattern

컴포지트 패턴은 간단한 객체나 복잡한 조합의 객체들의 상속관계를 같은 클라이언트 프로그램에서 나타내기 위하여 정의한다. 이 단순성 때문에 클라이언트는 좀더 간단해질 수 있는데, 노드와 잎들이 같은 방식으로 조절되기 때문이다. 

    컴포지트 패턴은 또한 컬렉션에 새로운 종류의 컴포넌트를 그것들이 같은 프로그래밍 인터페이스를 제공하는 동안 쉽게 추가할 수 있도록 한다. 반면에 이것은 지나치게 일반적이게 하는 단점을 가지고 있다. 

Other Implementation Issues

    Implementing the list in the parent. 만약 컴포지트에서 노드는 몇 안되고 거대한 양의 노드를 가지고 있다면, 각 잎에서의 빈 벡터 객체를 유지하는 것은 공간 문제와 관련이 있다. 하나의 대안적인 접근은 단지 getName()와 getValue()메소드 만을 갖는 Member 타입의 모든 객체들을  선언하는 것이다.그리고 나서  Member로부터 add, remove 와 elements 메소드를 구현한 Node 클래스를 상속 받는다. 비어 있는 벡터 enumerator들을 반환하는 대신 재귀적인 루프에서 체크를 할 수 있다. 
 

if(emp instanceof Node) {
	Enumeration e = emp.elements();
	while(e.hasMoreElements()) {
		Employee newEmp = (Employee)e.nextElement();
			//--------
	}

대부분의 경우에 이렇게 하여 공간을 절약한다는 주장은 명확하지가 않다. 

    Ordering components. 몇몇 프로그램에서, 컴포넌트의 순서는 중요할 수 있다. 예를 들어, 원래의 벡터를 알파벳순으로 정렬할 수 있고, 정렬된 새로운 벡터에 대한 Enumerator를 반환한다. 

'Development > 패턴자료' 카테고리의 다른 글

[펌] The Facade Pattern  (0) 2011.08.13
[펌] The Decorator Pattern  (0) 2011.08.13
[펌] The Command Pattern  (0) 2011.08.13
[펌] The Chain of Responsibility Pattern  (0) 2011.08.13
[펌] The Builder Pattern  (0) 2011.08.13
안정적인 DNS서비스 DNSEver DNS server, DNS service
Posted by 키르히아이스
,