정기간행물/daily

Composite 패턴

:)jun 2023. 9. 3. 20:57

요약 : 그릇과 내용물을 동일시한다. 복수와 단수를 동일시한다. (여러 개를 모아서 마치 하나의 것처럼 취급하는 것)

→ 복합 객체를 구성하는 개별적인 객체들을 모두 동일한 방법으로 다룰 수 있게 하는 패턴

예시 : 파일 시스템의 ‘file’과 ‘directory’

file과 directory는 서로 다르지만, 둘 다 ‘directory 안에 넣을 수 있는 것’ 이라는 공통 속성을 이용한다.

→ file과 directory를 directory entry이라고 부르고 같은 종류로 동일시하는 것 (composite 패턴의 조건)

예제 프로그램

클래스 목록

이름 설명

Entry File과 Directory를 동일시하는 추상 클래스
File 파일 클래스
Directory 디렉터리 클래스
Main 동작 테스트용 클래스

예제 프로그램의 클래스 다이어그램

Entry 클래스

추상 클래스이고 디렉터리 엔트리를 표현한다. 하위 클래스로 File, Directory 클래스가 만들어진다.

getName, getSize, printList(String prefix) 추상 메소드를 정의하고 있고 printList() 메서드를 구현해놨다.

그리고 getName이나 getSize를 하위 클래스에서 구현해 줄 것을 기대하고 toString도 오버라이딩으로 구현해놨다.

public abstract class Entry {
	// 이름을 얻는다.
  public abstract String getName();

	// 크기를 얻는다.
  public abstract int getSize();

	// 목록을 표시한다.
  public void printList() {
    printList("");
  }

	// prefix를 앞에 붙여서 목록을 표시한다.
  protected abstract void printList(String prefix);
	
	//문자열 표시
	@Override
  public String toString() {
    return getName() + "(" + getsize() + ")";
  }
}

File 클래스

public class File extends Entry {

  private String name;
  private int size;
 
  public File(String name, int size) {
    this.name = name;
    this.size = size;
  }

	@Override
  public String getName() {
    return name;
  }

	@Override
  public int getSize() {
    return size;
  }

	@Override
  protected void printList(String prefix) {
    System.out.println(prefix + "/" + this);
  }
}

Directory 클래스

public class Directory extends Entry {

	// 디렉토리 이름
  private String name;

	// 디렉토리 엔트리의 집합
  private List<Entry> directory = new ArrayList();

  public Directory(String name) {
    this.name = name;
  }

	// 이름을 얻는다
	@Override
  public String getName() { 
    return name;
  }

	// 크기를 얻는다
	@Override
  public int getSize() {
    int size = 0;
		for (Entry entry: directory) {
			size += entry.getSize();
    }
    return size;
  }

	@Override
  protected void printList(String prefix) {
    System.out.println(prefix + "/" + this);
    for (Entry entry: directory) {
      entry.printList(prefix + "/" + name);
    }
  }

	// 디렉터리 엔트리를 디렉터리에 추가한다
  public Entry add(Entry entry) {
    directory.add(entry);
    return this;
  }
}

Main 클래스

실행 과정

 

 

 

public class Main {
  public static void main(String[] args) {
    System.out.println("Making root entries...");
    Directory rootdir = new Directory("root");
    Directory bindir = new Directory("bin");
    Directory tmpdir = new Directory("tmp");
    Directory usrdir = new Directory("usr");
    rootdir.add(bindir);
    rootdir.add(tmpdir);
    rootdir.add(usrdir);
    bindir.add(new File("vi", 10000));
    bindir.add(new File("latex", 20000));
    rootdir.printList();

    System.out.println("");
    System.out.println("Making user entries...");
    Directory Kim = new Directory("Kim");
    Directory Lee = new Directory("Lee");
    Directory Park = new Directory("Park");
    usrdir.add(Kim);
    usrdir.add(Lee);
    usrdir.add(Park);
    Kim.add(new File("diary.html", 100));
    Kim.add(new File("Composite.java", 200));
    Lee.add(new File("memo.tex", 300));
    Park.add(new File("game.doc", 400));
    Park.add(new File("junk.mail", 500));
    rootdir.printList();
  }
}

실행 결과

예제 프로그램의 클래스 다이어그램

Composite 패턴의 클래스 다이어그램

Leaf 역

  • 내용물

Composite 역

  • 그릇
  • Leaf 역, Composite 역을 넣을 수 있다.

Component 역

  • Leaf 역과 Composite 역을 동일시하기 위한 역할

Client 역

  • Main 클래스
  • Composite 패턴의 사용자.

다른 예시

많은 도형을 다루는 툴을 만든다고 했을 때, 여러 개의 도형 묶음하나의 개별 도형과 똑같이 취급할 수 있도록 하기 위해 Composite 패턴을 사용할 수 있다.

Composite 패턴의 장단점

  • 장점
    • 새로운 개별 객체나 복합 객체를 추가하더라도 클라이언트가 코드를 수정할 필요가 없다.
    • 개별 객체와 복합 객체를 동일한 방식으로 다룰 수 있다. → 클라이언트 코드가 단순해진다.
    • 재귀적으로 탐색하거나 조작하는데 편해서 트리 구조를 다룰 때 유용하다.
  • 단점
    • 복합 객체 내부의 모든 개별 객체를 처리해야 한다.
    • 객체의 구조가 복잡한 경우에는 효과적이지만, 단순한 경우에는 오히려 설계의 복잡성을 증가시킬 수 있다.
    • 개별 객체와 복합 객체가 서로 다른 인터페이스를 가지는 경우나, 복합 객체 내부 구조가 동적으로 변하는 경우 Composite 패턴을 사용하기 힘들다