Notice
Recent Posts
Recent Comments
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
Archives
Today
Total
관리 메뉴

DeFacto-Standard IT

Composite Pattern 본문

Design Pattern/Structural Pattern

Composite Pattern

defacto standard 2017. 9. 26. 08:23

1. 가정


 - 리눅스 파일 시스템을 만든다.

 - 리눅스 파일 시스템은 파일, 디렉토리를 구성할 수 있다.

 - 디렉토리는 파일과 디렉토리를 여러 개 저장할 수 있다.

 - 파일, 디렉토리 추가와 현재 파일 또는 디렉토리의 이름을 출력하는 기능을 구현한다. (편의상 삭제, 수정 등은 구현하지 않음)

 - 디렉토리의 경우, 해당 디렉토리에 하위 디렉토리가 있다면, 하위 디렉토리안에 있는 파일과 디렉토리들의 정보까지 출력한다. 단, 이 조건은 말단 디렉토리까지 유효하다.



2. Naive Code


 - File

public class File {
private String name;

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

public void info(String prefix) {
System.out.println(prefix + this.name);
}
}

파일을 의미하는 File 클래스이다. info()는 파일에 대한 정보를 표현한다. prefix 변수의 경우 디렉토리의 하위 항목이라는 것을 보기 쉽도록 들여쓰기를 위해 사용한다.



 - Directory

public class Directory {

private String name;
private List<File> files;
private List<Directory> directories;

public Directory(String name) {
this.name = name;
this.files = new ArrayList<File>();
this.directories = new ArrayList<Directory>();
}

public void add(File file) {
this.files.add(file);
}

public void add(Directory directory) {
this.directories.add(directory);
}

public void info(String prefix) {

System.out.println(prefix + this.name);

for(File file: files)
file.info(prefix + "\t");

for(Directory directory: directories)
directory.info(prefix + "\t");

}
}

여러 개의 파일과 여러 개의 디렉터리를 저장할 수 있는 Directory 클래스이다.

File과 마찬가지로, 정보를 출력하면 해당 디렉터리에 존재하는 모든 파일의 이름과 모든 디렉터리의 이름을 출력한다. 만약, 하위 디렉토리에 파일이나 디렉토리가 있다면 그 정보까지 전부 출력한다.



 - NaiveClient

public class NaiveClient {
public static void main(String args[]) {
File file1 = new File("File 1");
File file2 = new File("File 2");
File file3 = new File("File 3");

Directory directory1 = new Directory("Directory 1");
Directory directory2 = new Directory("Directory 2");
Directory directory3 = new Directory("Directroy 3");

directory1.add(file1);
directory2.add(file2);
directory3.add(file3);

directory1.add(directory2);
directory1.add(directory3);

directory1.info("");
}
}

Directory 1은 <File 1 / Directory 2, 3>을 포함한다.

Directory 2는 <File 2>를 포함한다.

Directory 3은 <File 3>을 포함한다.


수행 결과는 다음과 같다.


3. 문제점


위 소스코드는 OCP를 위배한다. 현재는 2가지의 파일 종류만 존재하지만, 얼마든지 추가적인 파일 종류가 더해질 수 있기 때문이다.


예를 들어서, '디렉토리 안에 시스템 파일을 넣을 수 있으며, 이에 대한 정보 역시 출력해야 한다'는 요구사항이 추가된다면, Directory 클래스의 내용이 수정되어야 한다.



4. 해결방안


현재 문제가 되는 것은, Directory클래스에서 이를 구성하는 구체적인 클래스(File, Directory, SystemFile)들과의 강한 결합도 때문에 발생한다.


따라서 구체적인 요소에 대한 추상적인 개념을 선언하여 동일한 관점에서 다룰 필요가 있다.


이러한 추상적인 개념을 통해 추가/연산할 수 있게 구현한다면, 구체적인 요소(시스템파일)에 대한 Class가 추가되더라도 여전히 추상적인 개념으로 받아들이기 때문에 Directory 클래스는 변하지 않는다.


위 예제에서 Directory와 같이, 여러 요소들의 집합(구성)을 통해 완성되는 구성체를 Composite라고 표현한다.


그리고, 이 Composite를 구성하는 구체적인 요소들은 Leaf라고 한다.


이러한 구체적인 구성요소 Leaf를 추상화시킨 개념은 Component에 해당한다.


즉, '부분'을 담당하는 구체적인 구성요소 Leaf와, '전체'를 담당하는 구체적인 요소 Composite가

Composition을 통해 또 다시 '전체'를 담당하는 구체적인 요소 Composite를 구성하는 것이다.


단, Composite안에 Composite가 존재할 수 있다. 따라서, 반드시 Composite가 Leaf의 구성으로만 이루어진다는 보장은 없다. 


Composite입장에서는 이러한 Leaf와 Composite를 Component라는 추상적인 개념을 통해 일관된 관점에서 다루기 때문에, 위와 같은 설명이 나오게 되는 것이다.



5. Solution Code


 - Component

// Composite를 구성하는 추상적인 구성요소
public abstract class Component {
public abstract int getValue();
}

구체적인 구성요소인 Leaf와 Composite의 추상적인 개념인 Component이다. 


 - Leaf 1 ~ N

public class Leaf1 extends Component {
private int value;

public Leaf1(int value) {
this.value = value;
}

public int getValue() {
return value;
}

}

구체적인 구성요소에 해당한다. 예제에서는 Directory(Composite)를 이루는 File, SystemFile에 해당한다.


 - Composite

// 추상적인 구성요소 Component들로 구성된 구성체
public class Composite extends Component {

// Composite는 복수 개의 Component 객체들로 구성됨
private List<Component> components = new ArrayList<Component>();

// Component 객체를 Composite 클래스에 추가
public void addComponent(Component component) {
components.add(component);
}

// Component 객체를 Composite 클래스에서 제거
public void removeComponent(Component component) {
components.remove(component);
}

// 저장된 모든 Component의 value 총합
@Override
public int getValue() {
int sumValue = 0;

for (Component component : components)
sumValue += component.getValue();

return sumValue;
}

}

추상적인 구성요소 Component들로 구성되는 구성체 Composite이다. 예제에서는 Directory에 해당한다.


생성자를 통해 구체적인 구성요소를 주입받는 것이 아니라, 추상적인 구성요소를 추가하는 메서드(addComponent())를 사용해 구성요소를 받아들인다.


Component의 제거 또한 가능하나, 여기서는 사용하지 않았다.


Component는 인터페이스인데, '구성요소 Component들은, 각각 자신이 가진 값을 출력할 수 있다'는 것을 의미한다.


getValue()는 현재 자신에게 저장된 모든 Component에 대한 루프를 돌면서 value값을 얻는다.


주목할 만한 점은, Component로 구성된 Composite 역시 Component를 상속받아 하나의 구성요소로 본다는 것이다.


디렉토리(Composite) 안에는 디렉토리(Composite)가 추가될 수 있기때문에, Composite역시 Component이다.


 - Client

public class Client {
public static void main(String[] args) {
// Composite의 구성될 요소가 될 객체들을 생성
Leaf1 leaf1 = new Leaf1(10);
Leaf2 leaf2 = new Leaf2(20);
Leaf3 leaf3 = new Leaf3(30);
Leaf4 leaf4 = new Leaf4(40);

// Composite 객체를 생성하고 이를 구성할 객체들을 설정
Composite composite = new Composite();
composite.addComponent(leaf1);
composite.addComponent(leaf2);
composite.addComponent(leaf3);
composite.addComponent(leaf4);

int sumValue = composite.getValue();
System.out.println(sumValue);
}
}

구체적인 구성요소 Leaf 객체들을 선언하고, 구성체인 Composite 객체를 선언하여 addComponent를 통해 추가를 한다.


마지막에는 현재까지 구성된 Composite내의 Component의 value값을 종합하여 출력한다.


출력결과는 다음과 같다.



6. Composite Pattern


전체와 부분을 동일하게 취급하여 구성하는 경우에 사용된다.

Component는 Leaf와 Composite가 상속받는 추상 클래스이다. 따라서 Leaf와 Composite는 추상화되어 동일하게 취급될 수 있으며 Composite의 경우는 Component를 여러 개 가질 수 있으므로 List<Component> 변수를 사용하여 이를 가지고 있을 수 있다.


예를 들어, 리눅스 파일 시스템에서는 모든 것이 하나의 파일로 취급된다.


이때 Leaf는 File, SystemFile / Component는 LinuxFileSystem / Composite는 Directory 라고 했을 때,


File, SystemFile, Directory 모두 LinuxFileSystem 클래스를 상속받기 때문에, 파일/시스템파일/디렉토리에 대한 삭제, 추가, 정보보기와 같은 연산을 동일하게 적용할 수 있다.


Composite부분에서는 List로 Component들을 관리한다. 즉, 디렉토리 안에 파일과 디렉토리 모두 존재할 수 있다는 것이다.

NaiveCode에 적용하자면 '디렉토리가 디렉토리를 포함한다' 라고 해석할 수 있다.


List<Component>와 같이 추상화된 요소들을 담음으로서 Composite안의 List변수에는 Leaf와 Composite 모두가 같이 들어갈 수 있는 것이다.


- UML & Sequence Diagram




- Component : 구체적인 부분, 즉 Leaf 클래스와 전체에 해당하는 Composite 클래스에 공통 인터페이스를 정의한다.

- Leaf : 구체적인 부분 클래스로 Composite 객체의 부품으로 설정한다.

- Composite : 전체 클래스로 복수 개의 Component를 갖도록 정의한다. 그러므로 복수 개의 Leaf, 심지어 복수 개의 Composite 객체를 부분으로 가질 수 있다.


7. 장단점


'Design Pattern > Structural Pattern' 카테고리의 다른 글

Facade Pattern (기본)  (4) 2018.02.16
Bridge Pattern (기본)  (3) 2018.02.16
Adapter Pattern  (0) 2018.02.16
Proxy Pattern  (0) 2018.02.16
Decorator Pattern  (0) 2017.09.26
Comments