コンポジットパターン
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()を呼び出すと、そのすべての内容の合計サイズを再帰的に計算します。クライアントコードは、単一のファイルを作業対象にしているのか、それともフォルダー階層全体を対象にしているのかを知る必要はありません。どちらも同じメソッドに応答します。
チャレンジ
簡単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人の従業員、給与80000のAliceと給与75000のBobを含むEngineering部署を作成します。給与65000のCarolという1人の従業員を持つQA部署を作成します。次にTechnology部署を作成し、EngineeringとQAの両方をサブ部署として追加します。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: $"に続いて総給与予算を表示する
}
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。
オブジェクト指向プログラミングのすべてのレッスン
4Null Safety
Null Safety 入門Nullable と Non-Nullable? 演算子と ! 演算子late キーワードと Null SafetyNull-Aware 演算子クラスにおける Null Safetyまとめ:ユーザープロフィールシステム10コレクションとジェネリクス
List, Set, Map の概要型安全なコレクションジェネリッククラスジェネリックメソッドジェネリクスの制約Iterable と Iteratorまとめ:ジェネリックなストレージ