Menu
Coddy logo textTech

コンポジットパターン

CoddyのDartジャーニー「オブジェクト指向プログラミング」セクションの一部 — レッスン 99/110。

Compositeパターンは、個々のオブジェクトとオブジェクトのグループを均一に扱うことを可能にします。これは、単一の要素と要素の集合の両方が同じインターフェースを共有するツリー構造にオブジェクトを構成します。ファイルシステム、組織図、UIコンポーネントなどの階層構造を表現するのに最適です。

このパターンには、2種類の参加者がいます:Leaf ノード(子を持たない個別のオブジェクト)と Composite ノード(他のコンポーネントを保持するコンテナ)です。どちらも共通の Component インターフェースを実装します:

abstract class FileSystemItem {
  String get name;
  int getSize();
  void display([String indent = '']);
}

class File implements FileSystemItem {
  @override
  final String name;
  final int size;

  File(this.name, this.size);

  @override
  int getSize() => size;

  @override
  void display([String indent = '']) {
    print('$indent- $name ($size KB)');
  }
}

class Folder implements FileSystemItem {
  @override
  final String name;
  final List<FileSystemItem> _children = [];

  Folder(this.name);

  void add(FileSystemItem item) => _children.add(item);

  @override
  int getSize() => _children.fold(0, (sum, item) => sum + item.getSize());

  @override
  void display([String indent = '']) {
    print('$indent+ $name/');
    for (var child in _children) {
      child.display('$indent  ');
    }
  }
}

void main() {
  var docs = Folder('Documents');
  docs.add(File('resume.pdf', 150));
  docs.add(File('photo.jpg', 2400));

  var projects = Folder('Projects');
  projects.add(File('main.dart', 25));
  docs.add(projects);

  docs.display();
  print('Total: ${docs.getSize()} KB');
}

Folderクラスは、Fileオブジェクトと他のFolderオブジェクトの両方を含むことができます。フォルダーに対してgetSize()を呼び出すと、そのすべての内容の合計サイズを再帰的に計算します。クライアントコードは、単一のファイルを作業対象にしているのか、それともフォルダー階層全体を対象にしているのかを知る必要はありません。どちらも同じメソッドに応答します。

challenge icon

チャレンジ

簡単

Composite パターンを使用して組織図システムを構築しましょう!個々の従業員と、従業員や他の部署を含む部署の両方を一律に扱うことができる構造を作成します。これは企業の階層構造を表現するのに最適です。

コードを2つのファイルに整理します:

  • organization.dart: このファイルには、コンポーネントインターフェースと、リーフ(末端)クラスおよびコンポジット(複合)クラスの両方が含まれます。name ゲッター、int を返す getSalaryBudget() メソッド、および階層を表示するための display([String indent = '']) メソッドを持つ抽象クラス OrganizationUnit を作成してください。 OrganizationUnit を実装する Employee クラス(リーフ)を構築します。これには名前と給与が必要で、getSalaryBudget() は従業員の給与を返します。その display()[indent]- [name] ($[salary]) を出力する必要があります。 次に、同じく OrganizationUnit を実装する Department クラス(コンポジット)を作成します。これには名前が必要で、OrganizationUnit の子のリストを保持します。従業員やサブ部署を追加するための add() メソッドを追加してください。その getSalaryBudget() は、すべての子の予算を再帰的に合計する必要があります。その display()[indent]+ [name]/ を出力し、その後にインデントを増やして(スペースを2つ追加)各子を表示する必要があります。
  • main.dart: 組織ファイルをインポートし、会社の構造を構築します。2人の従業員、給与 80000Alice と給与 75000Bob を含む Engineering 部署を作成します。給与 65000Carol という1人の従業員を持つ QA 部署を作成します。次に Technology 部署を作成し、EngineeringQA の両方をサブ部署として追加します。Technology 部署で display() を呼び出し、その後に Total Budget: $ と合計給与予算を出力してください。

Composite パターンの素晴らしい点は、Technology.getSalaryBudget() がネストされたすべての部署と従業員の合計を自動的に計算することです。クライアントコードは内部構造を知る必要がありません!

期待される出力:

+ Technology/
  + Engineering/
    - Alice ($80000)
    - Bob ($75000)
  + QA/
    - Carol ($65000)
Total Budget: $220000

チートシート

Composite パターンは、個々のオブジェクトとオブジェクトのグループをツリー構造に構成することで、単一の要素とコレクションの両方が同じインターフェースを共有し、それらを一貫して扱えるようにします。

このパターンは、以下の要素で構成されています:

  • Component:共通の操作を定義する抽象インターフェース
  • Leaf:子を持たない個々のオブジェクト
  • Composite:他のコンポーネント(Leaf または Composite)を保持するコンテナ

例:ファイルシステム

abstract class FileSystemItem {
  String get name;
  int getSize();
  void display([String indent = '']);
}

class File implements FileSystemItem {
  @override
  final String name;
  final int size;

  File(this.name, this.size);

  @override
  int getSize() => size;

  @override
  void display([String indent = '']) {
    print('$indent- $name ($size KB)');
  }
}

class Folder implements FileSystemItem {
  @override
  final String name;
  final List<FileSystemItem> _children = [];

  Folder(this.name);

  void add(FileSystemItem item) => _children.add(item);

  @override
  int getSize() => _children.fold(0, (sum, item) => sum + item.getSize());

  @override
  void display([String indent = '']) {
    print('$indent+ $name/');
    for (var child in _children) {
      child.display('$indent  ');
    }
  }
}

void main() {
  var docs = Folder('Documents');
  docs.add(File('resume.pdf', 150));
  docs.add(File('photo.jpg', 2400));

  var projects = Folder('Projects');
  projects.add(File('main.dart', 25));
  docs.add(projects);

  docs.display();
  print('Total: ${docs.getSize()} KB');
}

Folder クラスは、File オブジェクトと他の Folder オブジェクトの両方を含むことができます。フォルダに対して getSize() を呼び出すと、その中身すべての合計サイズを再帰的に計算します。クライアントコードは、個々のファイルとフォルダ階層全体を一貫して扱うことができます。

自分で試してみよう

import 'organization.dart';

void main() {
  // TODO: エンジニアリング部門を作成する
  // TODO: 給与80000でAliceを追加する
  // TODO: 給与75000でBobを追加する
  
  // TODO: QA部門を作成する
  // TODO: 給与65000でCarolを追加する
  
  // TODO: テクノロジー部門を作成する
  // TODO: エンジニアリングとQAをサブ部門として追加する
  
  // TODO: テクノロジー部門でdisplay()を呼び出す
  
  // TODO: "Total Budget: $"に続いて総給与予算を表示する
}
quiz icon腕試し

このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。

オブジェクト指向プログラミングのすべてのレッスン