halang-log
🧵 Design Pattern

디자인 패턴 - Composite 패턴

date
Sep 26, 2022
slug
composite-pattern
author
status
Public
tags
디자인패턴
Composite 패턴
summary
Composite 패턴에 대해 알아보자
type
Post
thumbnail
category
🧵 Design Pattern
updatedAt
Nov 22, 2023 01:06 AM
언어

개요

소프트웨어 마에스트로 과정에서 멘토님, 팀원들과 디자인패턴 세미나를 진행하고 있습니다. 이번에는 Composite 패턴에 대해 설명드리겠습니다.
💡 Java언어로 배우는 디자인 패턴 입문 책을 참고하여 포스팅하였습니다.

Composite Pattern?

notion image
컴퓨터의 파일 시스템에는 디렉터리 라는 것이 있습니다. 디렉터리 안에는 파일이 있거나 하위 디렉터리가 있기도 합니다. 또 그 하위 디렉터리 안에는 다른 파일이나 하위 디렉터리가 있을 수 있습니다. 이처럼 디렉터리는 재귀적인 구조를 만듭니다.
디렉터리와 파일은 서로 다르지만 모두 디렉터리 안에 넣을 수 있는 것입니다. 따라서 디렉터리와 파일을 합해서 디렉터리 엔드리라는 이름으로 부르며 둘을 같은 종류로 간주하기도 합니다.
이번에 배울 Composite 패턴은 디렉터리와 파일을 동일시하여 재귀적인 구조를 만들기 위한 디자인 패턴입니다.

예제 프로그램

아래 표는 클래스 종류를 나타냅니다
이름
해설
Entry
File과 Directory를 동일시하는 추상 클래스
File
파일을 나타내는 클래스
Directory
디렉터리를 나타내는 클래스
FileTreatmentException
파일에 Entry를 추가하려고 할 때 발생하는 예외 클래스
Main
동작 테스트용 클래스
예제 프로그램의 클래스 다이어그램
notion image
  • 사용자인 Client는 Leaf와 Directory를 직접 참조하지 않고 공통 인터페이스인 Component를 참조합니다.

Entry 클래스

package composite; public abstract class Entry { public abstract String getName(); public abstract int getSize(); public Entry add(Entry entry) throws FileTreatmentException{ throw new FileTreatmentException(); } public void printList() { printList(""); } protected abstract void printList(String prefix); public String toString() { return getName() + " ("+getSize()+")"; } }
  • Entry 클래스는 추상클래스로 디렉터리 엔트리를 표현하며 하위 클래스인 File, Directory 클래스를 만듭니다.
  • 디렉터리 클래스의 이름을 얻기 위해 getName() 메소드가 있으며 크기를 얻기 위한 getSize() 메소드가 있습니다. 이들은 추상클래스로 하위 클래스에서 구현합니다.
  • 디렉터리 안에 디렉터리나 파일을 넣기 위해 add 메소드가 있습니다. 이 역시 하위클래스에서 구현합니다.
  • printList() 메소드는 인자값의 유무에 따라 오버로드됩니다. printList(String)은 protected로 Entry의 하위 클래스에서만 사용가능합니다.

File 클래스

package composite; public class File extends Entry { private String name; private int size; public File(String name, int size) { this.name = name; this.size = size; } public String getName() { return name; } public int getSize() { return size; } @Override protected void printList(String prefix) { System.out.println(prefix + "/" + this); } }
  • 파일을 표현하는 클래스이며 Entry 클래스의 하위 클래스로 선언합니다.
  • 파일의 이름을 표현하는 name, 크기를 나타내는 size 필드가 있습니다.
  • getName() 메소드는 파일의 이름을, getSize() 메소드는 파일의 크기를 반환합니다.
  • 상위 클래스인 Entry 클래스에서 위임된 printList(String)은 File 클래스에서 구현합니다.

Directory 클래스

package composite; import java.util.ArrayList; public class Directory extends Entry { private String name; private ArrayList directory = new ArrayList(); public Directory(String name) { this.name = name; } @Override public String getName() { return name; } public int getSize() { int size = 0; for (Object o : directory) { Entry entry = (Entry) o; size += entry.getSize(); } return size; } @Override public Entry add(Entry entry) { directory.add(entry); return this; } @Override protected void printList(String prefix) { System.out.println(prefix + "/" + this); for (Object o : directory) { Entry entry = (Entry) o; entry.printList(prefix + "/" + name); } } }
  • Directory 클래스는 Entry 클래스의 하위 클래스로 디렉터리를 표현합니다.
  • 디렉터리의 이름을 나타내는 name 필드와 디렉터리 엔트리의 집합을 나타내는 ArrayList인 directory가 있습니다.
  • 디렉터리의 크기를 동적으로 계산하여 구하고 있기 때문에 size 필드는 없습니다.
  • getSize() 메소드에서는 directory의 요소(디렉터리 or 파일)를 꺼내서 재귀적으로 해당 요소의 크기를 구한 것을 합합니다. 디렉터리와 파일 모두 Entry 하위 클래스의 인스턴스이기 때문에 동일한 메소드 getSize()로 크기를 얻을 수 있습니다.

FileTreatmentException 클래스

package composite; public class FileTreatmentException extends RuntimeException { public FileTreatmentException() { } public FileTreatmentException(String message) { super(message); } }
  • 파일에 대해서 add 메소드를 잘못 호출했을 때 제공되는 예외입니다.

Main 클래스

import composite.Directory; import composite.File; import composite.FileTreatmentException; public class Main { public static void main(String[] args) { try{ 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("Comosite.java", 200)); Lee.add(new File("memo.text", 300)); park.add(new File("game.doc", 400)); park.add(new File("junk.mail", 500)); rootdir.printList(); }catch(FileTreatmentException e){ e.printStackTrace(); } } }

Composite 패턴의 등장인물

Leaf (나뭇잎)의 역할

  • 예제 프로그램에서는 File 클래스
  • 내용물을 표시하는 역할을 하며 내부에는 다른 것을 넣을 수 없습니다.

Composite (복합체)의 역할

  • 예제 프로그램에서는 Directory 클래스
  • Leaf 역할이나 Composite 역할을 넣을 수 있습니다.

Component 역할

  • 예제 프로그램에서는 Entry 클래스
  • Leaf 역할과 Composite 역할을 동일시하는 역할을 합니다.
  • Leaf 역할과 Composite 역할에 공통적인 상위 클래스로 실현합니다.

Client 역할

  • 예제 프로그램에서는 Main 클래스
  • Composite 패턴의 사용자입니다.