結合モードの定義:Part-Wholeモードとも呼ばれます。単一オブジェクト(リーフノード)と結合オブジェクトを同じインターフェイスで表現することにより、単一オブジェクトと結合オブジェクトへのクライアントのアクセスに一貫性があります。これは、オブジェクトをツリーのような階層に結合するモードです。構造的なデザインパターンです。
結合モードは、一般的に全体と部分の関係を表すために使用され、オブジェクトをツリー構造に結合します。最上位ノードはルートノードと呼ばれ、最後のノードはリーフノードになり、中間ノードはブランチノードになります。 。ツリー構造は次の図のとおりです。
上の図からわかるように、ルートノードとブランチノードは基本的に同じデータ型に属し、コンテナとして使用できます。一方、リーフノードとブランチノードは意味的に同じタイプに属していませんが、結合モードでは、ブランチノードとリーフノードが同じデータ型(統一されたインターフェイスで定義されている)として扱われるため、一貫した動作が得られます。このように、結合モードでは、ツリー構造全体のオブジェクトが同じタイプに属し、クライアントはそれがブランチノードであるかリーフノードであるかを区別する必要がなく、直接操作できるため、非常に便利です。クライアントの使用に。
組み合わせモードの構造:組み合わせモードには、次の3つの役割が含まれます。
- 抽象ルートノード(コンポーネント):リーフノードとブランチノードのパブリックインターフェイスを宣言し、それらのデフォルトの動作を実装します。
- リーフノード(リーフ):組み合わせのリーフノードオブジェクト。子ノードはなく、ツリー構造の最後のレベルです。
- ブランチノード(複合):ブランチノードオブジェクトの組み合わせで、子ノードがあり、その主な機能は子ノードを格納および管理することです。
コンビネーションモードの実現:コンビネーションモードは、トランスペアレントコンビネーションモードとセーフコンビネーションモードに分けられます。透過的組み合わせモードと安全な組み合わせモードを使用して、会社の組織構造を例として取り上げましょう。
会社の組織構造を下図に示します。会社には、営業部門、研究開発部門、財務部門があります。
透過的な組み合わせモードの実現:
透過的組み合わせモードでは、抽象ルートノードはすべてのサブクラスのすべてのメソッドを宣言し、クライアントはリーフノードとブランチノードを区別する必要はありません。これらは一貫したインターフェイスを持ち、クライアントに対して透過的です。ただし、その欠点は次のとおりです。リーフノードは元々Add()を追加せず、Remove()を削除し、子ノードのGetChild()メソッドを取得しませんでしたが、それらを実装する必要があります(空の実装または例外をスローします)。問題。
//抽象根节点
public interface DeptComponent {
void add(DeptComponent deptComponent);
void remove(DeptComponent deptComponent);
List<DeptComponent> getChildren();
void getName();
}
//叶节点
public class LeafDept implements DeptComponent {
private String name;
public LeafDept(String name){
this.name = name;
}
@Override
public void add(DeptComponent deptComponent) { }
@Override
public void remove(DeptComponent deptComponent) { }
@Override
public List<DeptComponent> getChildren() {
return null;
}
@Override
public void getName() {
System.out.println(name+"前来报到!");
}
}
//树枝节点
public class CompositeDept implements DeptComponent{
private List<DeptComponent> children = new ArrayList<>();
private String name;
CompositeDept(String name){
this.name = name;
}
@Override
public void add(DeptComponent deptComponent) {
children.add(deptComponent);
}
@Override
public void remove(DeptComponent deptComponent) {
children.remove(deptComponent);
}
@Override
public List<DeptComponent> getChildren() {
return children;
}
@Override
public void getName() {
System.out.println(name+"前来报到!");
}
}
//测试类
public class CompositeTest {
public static void main(String[] args) {
DeptComponent company = new CompositeDept("某公司");
DeptComponent saleDept = new CompositeDept("销售部");
DeptComponent developmentDept = new CompositeDept("研发部");
DeptComponent financeDept = new CompositeDept("财务部");
DeptComponent saleA = new LeafDept("销售部A组");
DeptComponent saleB = new LeafDept("销售部B组");
DeptComponent developmentA = new LeafDept("研发部A组");
DeptComponent developmentB = new LeafDept("研发部B组");
developmentDept.add(developmentA);
developmentDept.add(developmentB);
saleDept.add(saleA);
saleDept.add(saleB);
company.add(saleDept);
company.add(developmentDept);
company.add(financeDept);
List<DeptComponent> children = company.getChildren();
children.stream().forEach(deptComponent -> {
deptComponent.getName();
deptComponent.getChildren().stream().forEach(deptComponent1 -> deptComponent1.getName());
});
}
}
透過的組み合わせモードの構造図:
安全な組み合わせモードの実現:
安全な組み合わせモードでは、リーフノードの管理方法がブランチノードに移されます。抽象ルートノードとリーフノードにはサブオブジェクトの管理方法がないため、透過的な組み合わせモードのセキュリティ問題を回避できます。リーフノードとブランチノードには、異なるインターフェイスの場合、クライアントは呼び出し時にリーフノードとブランチノードの存在を知る必要があるため、透過性が失われます。
//抽象根节点
public interface DeptComponent {
void getName();
}
//叶节点
public class LeafDept implements DeptComponent {
private String name;
public LeafDept(String name){
this.name = name;
}
@Override
public void getName() {
System.out.println(name+"前来报到!");
}
}
//树枝节点
public class CompositeDept implements DeptComponent{
private List<DeptComponent> children = new ArrayList<>();
private String name;
CompositeDept(String name){
this.name = name;
}
public void add(DeptComponent deptComponent) {
children.add(deptComponent);
}
public void remove(DeptComponent deptComponent) {
children.remove(deptComponent);
}
public List<DeptComponent> getChildren() {
return children;
}
@Override
public void getName() {
System.out.println(name+"前来报到!");
}
}
//测试类
public class CompositeTest {
public static void main(String[] args) {
CompositeDept company = new CompositeDept("某公司");
CompositeDept saleDept = new CompositeDept("销售部");
CompositeDept developmentDept = new CompositeDept("研发部");
CompositeDept financeDept = new CompositeDept("财务部");
DeptComponent saleA = new LeafDept("销售部A组");
DeptComponent saleB = new LeafDept("销售部B组");
DeptComponent developmentA = new LeafDept("研发部A组");
DeptComponent developmentB = new LeafDept("研发部B组");
developmentDept.add(developmentA);
developmentDept.add(developmentB);
saleDept.add(saleA);
saleDept.add(saleB);
company.add(saleDept);
company.add(developmentDept);
company.add(financeDept);
List<DeptComponent> children = company.getChildren();
children.stream().forEach(deptComponent -> {
deptComponent.getName();
if(deptComponent instanceof CompositeDept){
CompositeDept compositeDept = (CompositeDept) deptComponent;
compositeDept.getChildren().stream().forEach(deptComponent1 -> deptComponent1.getName());
}
});
}
}
安全な組み合わせモードの構造図:
複合モードの利点:
- 結合モードを使用すると、クライアントは単一のオブジェクトを処理でき、結合オブジェクトロジックは一貫しているため、クライアントコードが簡素化されます。
- 「開閉の原則」を満たすために、組み合わせで新しいオブジェクトを追加する方が簡単です。
複合モードのデメリット:
- 設計はより複雑であり、クライアントはクラス間の階層関係を明確にするためにより多くの時間を費やす必要があります。
- コンテナ内のコンポーネントを制限するのは簡単ではありません。
- 継承メソッドを使用してコンポーネントの新しい機能を追加するのは簡単ではありません。
複合モードの使用シナリオ:
- オブジェクトの全体と一部の階層構造を表す必要がある場合。
- クライアントが統合されたインターフェースで複合構造内のすべてのオブジェクトを使用できるように、複合オブジェクトと単一オブジェクトの違いをクライアントから隠す必要があります。