no_mkd
일.컴포짓 패턴이란?
컴포짓 패턴은 계층 구조를 제공하며 객체와 객체 집합 간의 차이를 무시할 수 있게 합니다
호출자는 손에 있는 것이 단일 객체인지 객체 그룹인지 알지 못하지만 괜찮습니다. 컴포짓 패턴에서는 호출자가 이를 알 필요가 없기 때문입니다
이.예를 들자면
파일 시스템을 설명한다고 가정해 봅시다. 파일 시스템에는 파일과 폴더가 있으며, 폴더 안에는 다시 폴더와 파일이 있습니다...
맞습니다, 이는 계층 구조입니다. 메뉴와 마찬가지로 메뉴에는 메뉴 항목과 하위 메뉴가 있고, 하위 메뉴에는 메뉴 항목과 하위 하위 메뉴가 있습니다... 계층 구조는 트리 구조이기도 합니다. Node 클래스를 정의하고 일련의 자식에 대한 포인터를 포함하여 완전한 트리를 구축하는 것을 쉽게 생각할 수 있습니다. 그러면 클래스 도표는 다음과 같을 것입니다:

주의: File 은 클래스 도표에 나열된 작업만 지원하며, Folder 클래스는 상속된 모든 작업을 지원합니다
클래스의 기본 설계는 이렇습니다. 이러한 클래스 구조를 사용하여 파일 시스템을 설명할 수 있습니다. 아래에서 코드 구현을 수행하겠습니다:
Directory 베이스 클래스를 정의합니다:
package CompositePattern;import java.util.ArrayList;
/**
-
定义目录类
-
@author ayqy */ public abstract class Directory { String name; String description; ArrayList<Directory> files;
/**
- 添加指定文件/文件夹到该目录下
- @param dir 将要添加的文件/文件夹
- @return 添加成功/失败 */ public boolean add(Directory dir){ throw new UnsupportedOperationException();//默认抛出操作异常 }
/**
- 删除该目录下的指定文件/文件夹
- @param dir 将要删除的文件/文件夹
- @return 删除成功/失败 */ public boolean remove(Directory dir){ throw new UnsupportedOperationException();//默认抛出操作异常 }
/**
- 清空该目录下所有文件和文件夹
- @return 清空成功/失败 */ public boolean clear(){ throw new UnsupportedOperationException();//默认抛出操作异常 }
public ArrayList<Directory> getFiles() { throw new UnsupportedOperationException();//默认抛出操作异常 }
/**
- 打印输出 */ public abstract void print();
public String getName() { return name; }
public String getDescription() { return description; }
public String toString(){ return name + description; } }
P.S. 베이스 클래스에서 Folder 고유의 메서드 처리 방식에 주의하십시오 (예외 throw). 물론 더 조화로운 방식으로 수행할 수도 있으며 각각 장단점이 있습니다. 후문에서 예외를 throw 하는 이러한 거친 방식을 채택하는 이유에 대해 자세히 설명합니다
주의, 베이스 클래스에서 추상적인 print 메서드를 정의했습니다. print 메서드를 호출하여 전체 파일 트리를 출력하고 싶습니다. 컴포짓 패턴은 이 번거로운 과정을 매우 쉽고 우아하게 구현할 수 있게 합니다
이제 File 클래스를 구현하겠습니다:
package CompositePattern;/**
-
实现文件类
-
@author ayqy */ public class File extends Directory{
public File(String name, String desc) { this.name = name; this.description = desc; }
@Override public void print() { System.out.print(this.toString());//输出文件自身信息 } }
File 클래스는 매우 간단합니다. 베이스 클래스에서 File 이 지원하지 않는 작업에 대해 기본 구현 (예외 throw) 을 수행했기 때문에 File 은 매우 날씬해졌습니다
다음은 Folder 클래스입니다:
package CompositePattern;import java.util.ArrayList;
/**
-
实现文件���类
-
@author ayqy */ public class Folder extends Directory{
public Folder(String name, String desc){ this.name = name; this.description = desc; this.files = new ArrayList<Directory>(); }
@Override public void print() { //打印该 Folder 自身信息 System.out.print(this.toString() + "("); //打印该目录下所有文件及子文件 for(Directory dir : files){ dir.print(); System.out.print(", "); } //打印文件夹遍历结束标志 System.out.print(")"); }
@Override public boolean add(Directory dir){ if(files.add(dir)) return true; else return false; }
@Override public boolean remove(Directory dir){ if(files.remove(dir)) return true; else return false; }
@Override public boolean clear(){ files.clear();
return true;}
@Override public ArrayList<Directory> getFiles() { return files; } }
Folder 클래스는 지원되는 모든 작업에 대해 자체 구현을 제공하며 print 메서드에서 약간의 기교를 부렸습니다. 매우 간단한 루프로 현재 노드의 모든 자손 노드 출력을 구현했습니다 (무엇을 연상시키십니까? 맞습니다, 데코레이터 패턴입니다). 언뜻 보면 믿기 어려울 수 있지만 이는 컴포짓 패턴 사용의 이점 중 하나입니다 (재귀에 대한 자연스러운 토양 제공)
삼.효과 예제
위에서 파일 시스템을 설명하는 데 필요한 클래스를 구현했습니다. 테스트하여 효과를 살펴보겠습니다:
테스트 클래스 코드는 다음과 같습니다:
package CompositePattern;/**
-
实现一个测试类
-
@author ayqy / public class Test { public static void main(String[] args){ /构造文件树/ / C a.txt b.txt system
sys.dat windows win32 settings log.txt win32.config */ Directory dir = new Folder("C", ""); dir.add(new File("a.txt", "")); dir.add(new File("b.txt", "")); Directory subDir = new Folder("system", ""); subDir.add(new File("sys.dat", "")); dir.add(subDir); Directory subDir2 = new Folder("windows", ""); Directory subDir3 = new Folder("win32", ""); subDir3.add(new Folder("settings", "")); subDir3.add(new File("log.txt", "")); subDir2.add(subDir3); subDir2.add(new File("win32.config", "")); dir.add(subDir2); dir.print();//打印输出文件树} }
실행 결과는 다음과 같습니다:
C(a.txt, b.txt, system(sys.dat, ), windows(win32(settings(), log.txt, ), win32.config, ), )
예상한 결과와 기본적으로 동일하지만 미중부족한 점이 있습니다: 불필요한 쉼표 구분 기호가 존재합니다. 불필요한 쉼표를 제거하려면 마지막 라운드를 순회할 때는 쉼표를 출력하지 않고 나머지 때는 쉼표를 출력하도록 명시적으로 루프해야 합니다
명시적인 이터레이터를 사용하여 구현하는 것을 쉽게 생각할 수 있습니다 (hasNext 가 마지막 라운드인지 판단하는 데 사용되지 않습니까? ArrayList 가 이터레이터를 지원한다는 것을 잊지 마십시오). print 메서드를 수정하겠습니다:
public void print() {
//打印该 Folder 自身信息
System.out.print(this.toString() + "(");
//打印该目录下所有文件及子文件
Iterator<Directory> iter = getFiles().iterator();
while(iter.hasNext()){
Directory dir = iter.next();
dir.print();
if(iter.hasNext()){
System.out.print(",");
}
}
//打印文件夹遍历结束标志
System.out.print(")");
}
거슬리는 불필요한 쉼표를 성공적으로 제거했습니다
사.조금 더 변경하기
연관 프로그램이 NotePad.exe 인 모든 파일 정보를 출력하는 방법
이제 File 에 새로운 속성 linkedExe 를 추가해야 합니다. 이는 해당 파일과 연관된 실행 프로그램을 나타냅니다. 폴더는 이 속성을 지원하지 않습니다 (여기서는 폴더가 linkedExe 속성을 지원하지 않는다고 규정하며, 폴더와 연관된 프로그램이 탐색기인지 다른 것인지 고려하지 않습니다)
새로운 요구사항을 구현하기 위해 일부 변경을 가해야 합니다. 타입 일관성을 얻기 위해 linkedExe 속성을 베이스 클래스 Directory 에 추가해야 합니다 (이렇게 하면 비난을 받을 수 있지만 때로는 일부 이점을 희생하여 다른 이점을 얻어야 합니다...)

직사각형 프레임 안의 내용은 우리가 추가한 새로운 것입니다. 이것들은 모두 File 이 지원하지만 Folder 는 지원하지 않는 것입니다. 이러한 변경을 한 후 연관 프로그램이 NotePad.exe 인 모든 파일 정보를 출력할 수 있습니다. 물론 Folder 클래스의 print 메서드도 수정해야 합니다:
public void print() {
//打印该目录下所有关联程序为 NotePad.exe 的文件
for(Directory dir : files){
try{
if("NotePad.exe".equalsIgnoreCase(dir.getLinkedExe())){
dir.print();
}
}catch(UnsupportedOperationException e){
//吃掉异常,继续遍历(Folder 不支持 getLinkedExe 操作)
}
}
}
무엇인가 발견하셨습니까? 컴포짓 패턴의 단점이 점점 더 분명해지고 있습니다
컴포짓 패턴은 객체와 객체 집합 간의 차이를 무시하고 동등하게 취급할 것을 요구합니다
맞습니다, 이렇게 하면 투명성을 얻었습니다 (print 메서드에서 현재 처리 중인 것이 File 인지 Folder 인지 알지 못합니다). 그러나 우리는 심지어 "동등하게 취급"하기 위해 예외 처리 메커니즘을 "남용"하여 객체 집합과 단일 객체의 차이를 숨깁니다. 이렇게 하는 것이 가치가 있는지는 구체적인 상황에 따라 다릅니다 (우리는 항상 일부 것을 희생하여 다른 더 유용한 것을 얻고 있으며, 이러한 희생이 가치가 있는지는 물론 고려해야 합니다)
오.이터레이터와 컴포짓 패턴
약속한 이터레이터는 어디 있습니까? 어디에도 보이지 않습니까? 어디에 있습니까?
이터레이터는 컴포짓 패턴에 숨어 있습니다. print 메서드 내부에서 계속 이터레이터를 사용하지 않았습니까? (암시적 이터레이터이거나 명시적 이터레이터입니다...)
위의 예에서 사용된 이터레이터는 내부 이터레이터라고 불립니다. 즉, 이터레이터는 컴포짓 패���의 구성 클래스에潜어 있기 때문에 발견하기 어렵습니다. 물론 원한다면 외부 이터레이터를 구축할 수도 있습니다. 다음과 같이:

DirectoryIterator 에서 hasNext 와 next 메서드를 구현하기 위해 현재 위치를 기록하는 스택 구조를 수동으로 유지해야 합니다 (내부 이터레이터는 시스템 스택에서 제공하는 지원입니다)
실제로 또 하나의 문제가 있습니다. File 클래스는 분명히 iterator 메서드를 지원하지 않지만 부모 클래스로부터 상속받았습니다. 어떻게 처리해야 할까요?
- null 반환: 호출자는 if 문을 사용하여 판단해야 합니다
- 예외 throw: 호출자는 예외 처리를 해야 합니다
- (권장 방법) 빈 이터레이터 (NullIterator) 반환: 빈 이터레이터는 어떻게 구현합니까? hasNext 가 false 를 반환하면 됩니다... 이렇게 하면 호출자에게 아무런 영향도 없습니다
육.요약
컴포짓 패턴이 제공하는 트리 계층 구조는 단일 객체와 객체 집합을 동등하게 취급할 수 있게 합니다 (작업상의 편의성을 얻음). 그러나 이러한 이점은 클래스의 단일 책임 원칙을 희생하여 얻은 것이며, 컴포짓 패턴은 상속을 사용하여 구현되므로 유연성이 부족합니다.
따라서 컴포짓 패턴을 사용할 때는 신중하게 고려해야 하며, 이러한 희생이 가치가 있는지 생각하고, 가치가 없다면 다른 디자인 패턴으로 대체할 수 있는지 고려해야 합니다...
칠.약간의 잡담 (예외 throw 여부)
때로는 null 반환, false 반환, 오류 코드 반환 등 예외를 throw 하지 않는 방식을 선택할 수 있습니다. 이러한 방식이 더 조화로울 수 있지만 예외를 throw 하는 것이 때로는 사실에 대한 가장 적절한 표현입니다
예를 들어 보겠습니다. File 클래스에 hasLinkedExe 속성이 있다고 가정합니다. 이는 연관된 애플리케이션이 존재하는지 여부를 나타냅니다. Folder 는 hasLinkedExe 속성을 지원하지 않으며, 동시에 이 속성은 부모 클래스로부터 상속되었으므로 삭제할 수 없습니다.
이때 false 를 반환하거나 예외를 throw 할 수 있습니다:
- false 반환: Folder 에 연관된 애플리케이션이 없음을 나타냅니다
- 예외 throw: Folder 가 이 작업을 지원하지 않음을 나타냅니다
분명히 예외를 throw 하는 의미가 우리가 진정으로 표현하고 싶은 것입니다
이렇게 많이 말했지만, 예외를 throw 하는 방식을 사용하기 위해 많은 노력을 기울였습니다. 더 적절하게 표현하기 위해서일까요? 아니요, 절대 그렇게 생각하지 마십시오. 이러한 약간의 의미 차이가 심각한 문제를 초래할 수 있습니다. 예를 들어:
연관된 애플리케이션이 없는 모든 파일 (즉, "알 수 없는 파일") 을 출력한다고 가정합니다. 만약当初 false 를 반환하는 방식을 채택하여 폴더가 이 작업을 지원하지 않음을 나타냈다면 잘못된 결과를 얻게 될 것입니다 (모든 알 수 없는 파일과 모든 폴더를 출력하게 됩니다...)
아직 댓글이 없습니다