3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

階層構造を扱うコードは、工夫を行わないと煩雑さが膨らんでいきます。
Compositeパターンを使用することで、美しく階層構造を表現することが出来ます。
本記事ではCompositeパターンについて、ファイルディレクトリの関係を模したコードを例に紹介したいと思います。

前提知識

  • C++の基礎文法を理解している

Compositeパターンとは

Composite(コンポジット)パターンとは、
個々の要素(リーフ)と、それらをまとめた集合(コンポジット)を同じインターフェースで扱えるようにするためのデザインパターンです。
葉とコンポジットの両方が同じインターフェースを実装することで、呼び出し側は構造の違いを意識せずに操作できるようになります。
その反面で、再帰的に処理を委譲するためデバッグが難しくなるといったデメリットも存在します。

Compositeパターンの用語と構成

Compositeパターンは以下の要素で構成されます。

  • Component/コンポーネント
    • 共通のインターフェース
    • リーフコンポジットを同一視するための窓口
  • Leaf/リーフ
    • 子を持たない要素
    • 実際の処理を行う最小単位
  • Composite/コンポジット
    • 子を持つ要素
    • 内部に複数のComponentを保持し、処理を委譲する

構造の図解

サンプルコード

Component.h
//--------------------------------------------------------------
//! @file   Component.h
//! @brief  CompositeパターンのComponentインターフェースクラスの定義
//! @author つきの
//--------------------------------------------------------------
#pragma once
#include <iostream>
#include <memory>
#include <string>
#include <vector>
//--------------------------------------------------------------
//! @class   Component
//! @brief	 Compositeパターンのすべての基底クラス
//! @details CompositeパターンのComponentに相当
//--------------------------------------------------------------
class Component {
public:
	//--------------------------------------------------------------
	//! @brief 仮想デストラクタ
	//--------------------------------------------------------------
	virtual ~Component() = default;

	//--------------------------------------------------------------
	//! @brief コンポーネントの情報を表示する純粋仮想関数
	//! @param indent インデントの深さ
	//--------------------------------------------------------------
	virtual void print(int indent = 0) const = 0;
};
Directory.h
//--------------------------------------------------------------
//! @file   Directory.h
//! @brief  ファイルシステムのディレクトリを表すクラスの定義
//! @author つきの
//--------------------------------------------------------------
#pragma once
#include "Component.h"
//--------------------------------------------------------------
//! @class Directory
//! @brief ファイルシステムのディレクトリを表すクラス
//! @details CompositeパターンのCompositeに相当
//--------------------------------------------------------------
class Directory : public Component {
public:
	//--------------------------------------------------------------
	// コンストラクタ
	//! @param name ディレクトリ名
	//--------------------------------------------------------------
	explicit Directory(std::string name);

	//--------------------------------------------------------------
	// ディレクトリにコンポーネントを追加する
	//! @param entry 追加するコンポーネント
	//--------------------------------------------------------------
	void add(std::shared_ptr<Component> entry);

	//--------------------------------------------------------------
	// コンポーネントの情報を表示する
	//! @param indent インデントの深さ
	//--------------------------------------------------------------
	void print(int indent = 0) const override;

private:
	std::string name_;                                  //!< ディレクトリ名
	std::vector<std::shared_ptr<Component>> children_;  //!< ディレクトリ内のコンポーネント一覧
};
Directory.cpp
//--------------------------------------------------------------
//! @file   Directory.cpp
//! @brief  ファイルシステムのディレクトリを表すクラスの実装
//! @author つきの
//--------------------------------------------------------------
#include "Directory.h"
//--------------------------------------------------------------
//! @brief コンストラクタ
//--------------------------------------------------------------
Directory::Directory(std::string name)
	: name_(std::move(name)) {
}

//--------------------------------------------------------------
//! @brief ディレクトリにコンポーネントを追加する
//--------------------------------------------------------------
void Directory::add(std::shared_ptr<Component> entry) {
	children_.push_back(std::move(entry));
}

//--------------------------------------------------------------
//! @brief コンポーネントの情報を表示する
//--------------------------------------------------------------
void Directory::print(int indent) const {
	// ディレクトリ名を表示
	std::cout << std::string(indent, ' ') << "[ " << name_ << " ]\n";
	// 子コンポーネントを表示
	for (const auto& child : children_) {
		child->print(indent + 2);
	}
}
File.h
//--------------------------------------------------------------
//! @file   File.h
//! @brief  ファイルシステムのファイルを表すクラスの定義
//! @author つきの
//--------------------------------------------------------------
#pragma once
#include "Component.h"
//--------------------------------------------------------------
//! @class File
//! @brief ファイルシステムのファイルを表すクラス
//! @details CompositeパターンのLeafに相当
//--------------------------------------------------------------
class File : public Component {
public:
	//--------------------------------------------------------------
	// コンストラクタ
	//! @param name ファイル名
	//--------------------------------------------------------------
	explicit File(std::string name);

	//--------------------------------------------------------------
	//! @brief コンポーネントの情報を表示する
	//! @param indent インデントの深さ
	//--------------------------------------------------------------
	void print(int indent = 0) const override;

private:
	std::string name_;  //!< ファイル名
};
File.cpp
//--------------------------------------------------------------
//! @file   File.cpp
//! @brief  ファイルシステムのファイルを表すクラスの宣言
//! @author つきの
//--------------------------------------------------------------
#include "File.h"
//--------------------------------------------------------------
//! @brief コンストラクタ
//--------------------------------------------------------------
File::File(std::string name)
	: name_(std::move(name)) {
}

//--------------------------------------------------------------
//! @brief コンポーネントの情報を表示する
//--------------------------------------------------------------
void File::print(int indent) const {
	std::cout << std::string(indent, ' ') << "- " << name_ << "\n";
}
main.cpp
//--------------------------------------------------------------
//! @file   main.h
//! @brief  Compositeパターンのサンプルコード
//! @author つきの
//--------------------------------------------------------------
#include "Directory.h"
#include "File.h"
// エントリポイント
int main() {
    //--------------------------------------------------------------
	// ルートにディレクトリを作る
    //--------------------------------------------------------------
	auto root = std::make_shared<Directory>("root");    // ルートディレクトリ

    //--------------------------------------------------------------
	// rootにhomeディレクトリを追加する
    //--------------------------------------------------------------
	auto home = std::make_shared<Directory>("home");    // homeディレクトリを作って
	root->add(home);									// rootに追加		
	
	//--------------------------------------------------------------
	// homeにファイルを追加する
	//--------------------------------------------------------------
	auto test_file = std::make_shared<File>("test.txt");   // test.txtファイルを作って
	home->add(test_file);                                  // homeに追加

    // 階層構造の表示
    root->print();
	// プログラム終了
	return 0;
}
result
[ root ]
  [ home ]
    - test.txt

階層構造が表現できました。

クラス図

Component/コンポーネント

Componentは、ファイルとディレクトリ両方の共通インターフェースです。
これを継承することで、Compositeから階層に追加することが可能になります。

Component.cpp
//--------------------------------------------------------------
//! @class   Component
//! @brief	 Compositeパターンのすべての基底クラス
//! @details CompositeパターンのComponentに相当
//--------------------------------------------------------------
class Component {
public:
	//--------------------------------------------------------------
	//! @brief 仮想デストラクタ
	//--------------------------------------------------------------
	virtual ~Component() = default;

	//--------------------------------------------------------------
	//! @brief コンポーネントの情報を表示する純粋仮想関数
	//! @param indent インデントの深さ
	//--------------------------------------------------------------
	virtual void print(int indent = 0) const = 0;
};

Composite/コンポジット

階層構造の子として、Component継承したオブジェクトを追加できる関数を作成します。リーフ(本記事ではFile)とコンポジット(本記事ではDirectory)が共にComponent継承しているため、どちらも追加することができます。

Directory.h
	//--------------------------------------------------------------
	// ディレクトリにコンポーネントを追加する
	//! @param entry 追加するコンポーネント
	//--------------------------------------------------------------
	void add(std::shared_ptr<Component> entry);
Directory.cpp
//--------------------------------------------------------------
//! @brief ディレクトリにコンポーネントを追加する
//--------------------------------------------------------------
void Directory::add(std::shared_ptr<Component> entry) {
	children_.push_back(std::move(entry));
}

Leaf/リーフ

Leafは、子を持たない要素で、階層構造ではくっつけられることだけができるオブジェクトです。
本記事で情報表示とコンストラクタのみを実装しています。

File.h
//--------------------------------------------------------------
//! @class File
//! @brief ファイルシステムのファイルを表すクラス
//! @details CompositeパターンのLeafに相当
//--------------------------------------------------------------
class File : public Component {
public:
	//--------------------------------------------------------------
	// コンストラクタ
	//! @param name ファイル名
	//--------------------------------------------------------------
	explicit File(std::string name);

	//--------------------------------------------------------------
	//! @brief コンポーネントの情報を表示する
	//! @param indent インデントの深さ
	//--------------------------------------------------------------
	void print(int indent = 0) const override;

private:
	std::string name_;  //!< ファイル名
};

File.cpp
//--------------------------------------------------------------
//! @brief コンストラクタ
//--------------------------------------------------------------
File::File(std::string name)
	: name_(std::move(name)) {
}

//--------------------------------------------------------------
//! @brief コンポーネントの情報を表示する
//--------------------------------------------------------------
void File::print(int indent) const {
	std::cout << std::string(indent, ' ') << "- " << name_ << "\n";
}

総括

  • Compositeパターンを使用することで、階層構造を美しく管理することができる
  • 再帰的な管理のため、デバッグが難しくなってしまうというデメリットも存在する
  • Component(共通インターフェース)を継承することで、アップキャストによってCompositeCompositeLeafを同じように管理することができる
3
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?