Composite Parttern이란?
컴파지트 패턴은 집합체와 집합체를 구성하는 단일체를 같은 개념으로 처리하는 구조적 디자인 패턴이다.
이 패턴을 사용하면 개별 객체와 객체 그룹을 동일하게 다룰 수 있어서
객체를 트리 구조로 구성하여 부분-전체 계층을 표현할 수 있다.
왜 동일한 개념으로 처리하면 트리 구조로 구성할 수 있느냐?가 궁금하다면
아래의 컴포지트 패턴의 UML 다이어그램을 보면 알 수 있다.
Unit: Folder와 File의 공통의 type == composite
File: 단일체
Folder: 집합체
Folder안에 File도 들어가지만 또다른 sub Folder가 들어갈 수 있다.
이때 여러번 Folder안에 Folder와 File을 넣는 과정에서
우리는 맨 마지막 폴더가 tree 구조의 맨 마지막 leaf노드이고 맨 최상단 Folder가 root인 트리를 확인할 수 있다.
Composite Pattern을 쓰는 이유(==기존 개발 형태에 대한 문제점)?
만일 File과 Folder를 같은 타입으로 두지 않고 각각의 클래스로만 존재한다면 어떻게 될까?
File 클래스
public class File {
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
public String toString() {
return name + "(" + size + ")";
}
}
폴더 클래스
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class Folder {
private String name;
private List<Object> contents = new ArrayList<>();
public Folder(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void add(Object obj) {
contents.add(obj);
}
public int getSize() {
int totalSize = 0;
Iterator<Object> it = contents.iterator();
while (it.hasNext()) {
Object obj = it.next();
if (obj instanceof File) {
totalSize += ((File) obj).getSize();
} else if (obj instanceof Folder) {
totalSize += ((Folder) obj).getSize();
}
}
return totalSize;
}
public void list(String indent) {
System.out.println(indent + "+ " + name + " (" + getSize() + ")");
Iterator<Object> it = contents.iterator();
while (it.hasNext()) {
Object obj = it.next();
if (obj instanceof File) {
System.out.println(indent + " " + obj);
} else if (obj instanceof Folder) {
((Folder) obj).list(indent + " ");
}
}
}
}
getSize() 메서드에 주목해보자.
public int getSize() {
int totalSize = 0;
Iterator<Object> it = contents.iterator();
while (it.hasNext()) {
Object obj = it.next();
if (obj instanceof File) {
totalSize += ((File) obj).getSize();
} else if (obj instanceof Folder) {
totalSize += ((Folder) obj).getSize();
}
}
return totalSize;
}
이제 딱봐도 확장에 닫혀있는게 보이지 않는가?
그리고 이게 File인지 Folder인지 계속 타입검사와 캐스팅을 반복하고 있다.
문제점
1. 코드의 중복과 복잡성
기존 개발 방식에서는 파일과 폴더를 처리하기 위해 타입 검사와 캐스팅을 반복적으로 수행해야함. 이는 코드의 중복을 초래하고, 복잡성을 증가시킴.
2. 유지보수성의 어려움
파일과 폴더의 구조가 변경되거나 새로운 타입의 파일 또는 폴더가 추가될 경우, 모든 관련된 코드에서 타입 검사와 캐스팅 부분을 수정해야함. 이는 유지보수를 어렵게 만들고, 버그가 발생할 가능성을 높임.
예를들어 File 중에서
Special File이라는 것이 생겨나면?
public class SpecialFile extends File {
// 어떤 필드값과 메서드가 존재한다고 하자
}
그럼 타입 검사와 캐스팅을 반복해야한다..
public int getSize() {
int totalSize = 0;
for (Object obj : contents) {
if (obj instanceof File) {
totalSize += ((File) obj).getSize();
} else if (obj instanceof SpecialFile) {
totalSize += ((SpecialFile) obj).getSize();
} else if (obj instanceof Folder) {
totalSize += ((Folder) obj).getSize();
}
}
return totalSize;
}
이런 문제점이 생겨나기 때문에 컴포지트 패턴을 사용해서 편리하고 유지보수하기 쉽도록 구현해보자.
Composite Pattern(컴포지트 패턴) 구현 방법
1. 컴포넌트(Component) 인터페이스
2. 잎(Leaf) 클래스
3. 복합 객체(Composite) 클래스
가장 대표적인 예시인 파일 시스템에 대해서 코드를 짜보자.
폴더 안에 파일을 넣고, 폴더를 넣는 과정은 있되 삭제하는 건 없다고 해보자.
우리는 file과 folder의 총 크기를 확인해보겠다.
1. 컴포넌트(Component) 인터페이스
트리 구조의 모든 객체가 공유하는 인터페이스를 정의한다. 클라이언트는 이 컴포넌트를 통해 처리함.
public abstract class Unit {
private String name;
public Unit(String name) {
this.name = name;
}
public String getName() {
return name;
}
@Override
public String toString() {
return anme + "(" + getSize() + ")";
}
public abstract int getSize();
}
2. 잎(Leaf) 클래스
트리 구조의 단일 객체로, 실제 작업을 수행하는 개별 객체이다.
public class File extends Unit {
public int size;
public File(String name, int size) {
super(name);
this.size = size;
}
@Override
public int getSize() {
return size;
}
}
3. 복합 객체(Composite) 클래스
트리 구조의 복합 객체로, 자식 컴포넌트를 관리하고 작업을 자식에게 위임한다.
public class Folder extends Unit {
private LinkedList<Unit> unitList = new LinkedList<>();
public Folder(String name) {
super(name);
}
@Override
public int getSize() {
int size = 0;
Iterator<Unit> it = unitList.iterator();
while (it.hasNext()) {
Unit unit = it.next();
size += unit.getSize();
}
return size;
}
public boolean add(Unit unit) {
unitList.add(unit);
return true;
}
// 해당 부분은 컴포지트 패턴의 일부가 아닌 출력했을 때 조금 더 계층적인 것을
// 보여주기 위한 메서드들임.
// 그러나 컴포지트 패턴에서 집합체와 단일체를 같은 타입으로 바라보기 때문에 이런 재귀적인 코드가 나오는 것은 당연한 것을 인지
private void list(String indent, Unit unit) {
if (unit instanceof File) {
System.out.println(indent + unit);
} else {
Folder dir = (Folder) unit;
Iterator<Unit> it = dir.unitList.iterator();
System.out.println(indent + "+ " + unit);
while (it.hasNext()) {
list(indet + " " , it.next());
}
}
}
public void list() {
list("", this);
}
}
클라이언트 코드
public class CompositePattern {
public static void main(String[] args) {
Folder schoolFolder = new Folder("학교");
Folder grade1Folder = new Folder("1학년");
Folder grade2Folder = new Folder("2학년");
schoolFolder.add(grade1Folder);
schoolFolder.add(grade2Folder);
File enterPhoto = new File("입학사진", 256);
grade1Folder.add(enterPhoto);
Folder sem1Folder = new Folder("1학기");
Folder sem2Folder = new Folder("2학기");
grade2Folder.add(sem1Folder);
grade2Folder.add(sem2Folder);
File lecturePlan = new File("강의계획서", 120);
sem1Folder.add(lecturePlan);
Folder projFolder = new Folder("프로젝트");
sem2Folder.add(projFolder);
File draft = new File("드래프트", 488);
File finalResult = new File("결과물", 560);
projFolder.add(draft);
projFolder.add(finalResult);
schoolFolder.list();
}
}
결과
+ 학교(1424)
+ 1학년(256)
입학사진(256)
+ 2학년(1168)
+ 1학기(120)
강의계획서(120)
+ 2학기(1048)
+ 프로젝트(1048)
드래프트(488)
결과물(560)
Composite Pattern의 장단점
장점
- 트리 구조 표현:
- 복잡한 객체 구조를 트리 형태로 표현할 수 있음.
- 일관된 처리:
- 개별 객체와 복합 객체를 동일하게 처리할 수 있다. 클라이언트는 개별 객체와 복합 객체를 구분할 필요 없이 동일한 인터페이스를 사용할 수 있음.
- 유연한 구조:
- 새로운 종류의 객체를 추가하거나 기존 객체를 수정할 때, 최소한의 변경만으로 가능함. 트리 구조의 각 요소가 동일한 인터페이스를 따르기 때문에 유연하게 구조를 변경할 수 있음.
단점
- 복잡성 증가:
- 객체의 구성과 구조가 복잡해질 수 있다. 특히 트리 구조가 깊어질수록 관리가 어려워질 수 있음.
- 성능 문제:
- 트리 구조를 순회하거나 작업을 위임하는 과정에서 성능이 저하될 수 있음. 특히 대규모 객체 구조에서는 성능 최적화가 필요할 수 있음.
Composite Pattern 사용 사례
- 그래픽 사용자 인터페이스(GUI):
- 버튼, 텍스트 필드, 패널 등 다양한 GUI 요소를 트리 구조로 구성하여 복잡한 UI를 단순하게 관리할 수 있음.
- 파일 시스템:
- 파일과 디렉토리의 계층 구조를 표현하고 관리하는 데 사용됨.
- 문서 구조:
- 문서의 각 섹션을 트리 구조로 구성하여, 문서의 구조를 쉽게 관리하고 변경할 수 있음.
'CS > 디자인패턴' 카테고리의 다른 글
[Design Pattern] Facade Pattern(퍼사드 패턴) (0) | 2024.06.05 |
---|---|
[Design Pattern] Decorator Pattern(데코레이터 패턴) (0) | 2024.06.05 |
[Design Pattern] Proxy Pattern(프록시 패턴) (0) | 2024.06.03 |
[Design Pattern] Strategy Pattern(전략 패턴) (0) | 2024.06.02 |
[Design Pattern] Factory Pattern(팩토리 패턴) (0) | 2024.06.01 |