コンポジットパターン

📢 この記事は gemini-2.5-flash によって翻訳されました

コンポジットパターン オブジェクト構造パターン

意図

オブジェクトをツリー構造に組み合わせて「部分 - 全体」の階層を表現するんだ。コンポジットパターンを使うと、単一のオブジェクトと複合オブジェクトを同じように扱えるようになるよ。

構造

組合模式

ここで、

  • Component:複合構造内のオブジェクトのインターフェースを宣言するんだ。適切な場合には、すべてのクラスで共通のインターフェースのデフォルトの振る舞いを実装するよ。Componentの子コンポーネントにアクセスして管理するためのインターフェースも宣言するね。オプションだけど、再帰的な構造で親コンポーネントにアクセスするためのインターフェースを定義して、適切な場合にそれを実装することもできるよ。
  • Leaf:複合構造の葉ノードオブジェクトを表すよ。葉ノードには子ノードがないんだ。複合構造でプリミティブオブジェクトの振る舞いを定義するね。
  • Composite:すべての子コンポーネントを持つコンポーネントの振る舞いを定義するよ。子コンポーネントを格納して、Componentインターフェースで子コンポーネントに関する操作を実装するんだ。
  • Client:Componentインターフェースを通じて複合コンポーネントのオブジェクトを操作するよ。

適用性

コンポジットパターンは、こんな時に役立つよ:

  • オブジェクトの部分 - 全体階層を表現したい時。
  • ユーザーが複合オブジェクトと単一オブジェクトの違いを意識せず、複合構造内のすべてのオブジェクトを一様に扱ってほしい時。

例子 1

ある会社の組織図が下の図のようになっているとするね。

組合模式-例1-図1

ここで、コンポジット(Composition)デザインパターンを使ってこの会社の組織構造を構築すると、下の図のようなクラス図になるよ。

組合模式-例1-図2

Companyは抽象クラスで、組織図に支社/事業所または部署を追加(Add)したり削除(Delete)したりするメソッドインターフェースを定義しているんだ。ConcreteCompanyクラスは具体的な支社や事業所を表していて、支社や事業所の配下には異なる部署を設置できるよ。HRDepartmentクラスとFinanceDepartmentクラスはそれぞれ人事部と財務部を表しているね。

 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
32
33
34
35
36
37
38
39
40
41
42
43
44
import java.util.*;

abstract class Company {
    protected String name;
    public Company(String name) {   this.name   = name; }
    public abstract void Add(Company c); // 子会社、事業所、または部署を追加する
    public abstract void Delete(Company c); // 子会社、事業所、または部署を削除する
}

class ConcreteCompany extends Company {
    private List<Company> children = new ArrayList<Company>();
    // 子会社、事業所、または部署を格納する
    public ConcreteCompany(String name) { super(name); }
    public void Add(Company c) {children.add(c); }
    public void Delete(Company c) {children.remove(c); }
}

class HRDepartment extends Company {
    public HRDepartment(String name) { super(name); }
// その他のコードは省略
}

class FinanceDepartment extends Company {
    public FinanceDepartment(String name) { super(name); }
// その他のコードは省略
}

public class Test {
    public static void main(String[] args) {
        ConcreteCompany root = new ConcreteCompany("北京総公司");
        root.Add(new HRDepartment("総公司人力資源部"));
        root.Add(new FinanceDepartment("総公司財務部"));

        ConcreteCompany comp = new ConcreteCompany("上海分公司");
        comp.Add(new HRDepartment("上海分公司人力資源部"));
        comp.Add(new FinanceDepartment("上海分公司財務部"));
        root.Add(comp);

        ConcreteCompany comp1 = new ConcreteCompany("南京办事处");
        comp1.Add(new HRDepartment("南京办事处人力資源部"));
        comp1.Add(new FinanceDepartment("南京办事处財務部"));
        comp.Add(comp1); // その他のコードは省略
    }
}

例子 2

カスケードメニューは、ウィンドウ形式のソフトウェアシステムでよく使われるシステム機能の整理方法だよね。カスケードメニューには、メニュー項目(特定の機能に直接対応するもの)が含まれることもあれば、サブメニューが含まれることもあるんだ。ここで、コンポジット(composite)デザインパターンを使ってカスケードメニューを実装すると、下の図のようなクラス図になるよ。

組合模式-例2

 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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
import java.util.*;

abstract class MenuComponent {  // カスケードメニューを構成する要素
    protected String name;      // メニュー項目またはサブメニュー名
    public void printName() { System.out.println(name); }
    public abstract boolean addMenuElement(MenuComponent element) ;
    public abstract boolean removeMenuElement(MenuComponent element);
    public abstract List<MenuComponent> getElement();
}

class MenuItem extends MenuComponent {
    public MenuItem(String name) { this.name=name; }
    public boolean addMenuElement(MenuComponent element) { return false; }
    public boolean removeMenuElement(MenuComponent element) {
        return false;
    }
    public List<MenuComponent> getElement(){ return null; }
}

class Menu extends MemuComponent {
    private   List<MenuComponent> elementsList;
    public Menu(String name) {
        this.name = name;
        this.elementList = new ArrayList<MenuComponent>;
    }
    public boolean addMenuElement(MenuComponent element) {
        return elementList.add(element);
    }
    public boolean removeMenuElement(MenuComponent element) {
        return elementList.remove(element);
    }
    public List<MenuComponent> getElement() { return elementList; }
}

class CompositeTest {
    public static void main(String[] args) {
        MenuComponent mainMenu = new Menu("Insert");
        MenuComponent subMenu = new Menu("Chart");
        MenuComponent element = new MenuItem("On This Sheet");
        mainMenu.addMenuElement(subMenu);
        subMenu.addMenuElement(element);
        printMenus(mainMenu);
    }

    private static void printMenus(MenuComponent ifile) {
        ifile.printName();
        List<MenuComponent> children = ifile.getElement();
        if (children == null) return;
        for(MenuComponent element; children) {
            printMenus(element);
        }
    }
}

例子 3

 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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
import java.util.*;
public class CompositePattern {
    public static void main(String[] args) {
        AbstractFile root = new Folder("root");

        AbstractFile bin = new Folder("bin");
        AbstractFile tmp = new Folder("tmp");
        AbstractFile file = new File("file");

        root.Add(bin);
        bin.Add(tmp);
        root.Add(file);

        // root.Remove(tmp);
        print(root);
    }

    static void print(AbstractFile file){
        List<AbstractFile> lf = file.GetChildren();

        file.Operation();

        // より簡潔な方法
        // if(lf == null) return;
        // for(AbstractFile i : lf) print(i);

        if (lf != null) {
            for (AbstractFile i : lf) {
                if (i != null) {
                    print(i);
                }
                else
                    return;
            }
        }
        else
            return;
    }
}

abstract class AbstractFile{
    protected String name;

    public void Operation(){
        System.out.println(name);
    }

    public abstract boolean Add(AbstractFile af);
    public abstract boolean Remove(AbstractFile af);
    public abstract List<AbstractFile> GetChildren();
}

class Folder extends AbstractFile{
    private List<AbstractFile> childrenList = new ArrayList<>();

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

    @Override
    public boolean Add(AbstractFile af){
        return childrenList.add(af);
    }

    @Override
    public boolean Remove(AbstractFile af){
        return childrenList.remove(af);
    }

    @Override
    public List<AbstractFile> GetChildren(){
        return this.childrenList;
    }
}

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

    @Override
    public boolean Add(AbstractFile af){
        System.out.println("Forbidden");
        return false;
    }

    @Override
    public boolean Remove(AbstractFile af){
        System.out.println("Forbidden");
        return false;
    }

    @Override
    public List<AbstractFile> GetChildren(){
        return null;
    }
}

Visits Since 2025-02-28

Hugo で構築されています。 | テーマ StackJimmy によって設計されています。