Compositeパターンを試してみる

プログラミング

こんにちは。

なんちゃってPascalを作っている途中です。

前回、Compositeパターンがうまく作りきれなくて中途半端になってしまいました。

おそらくコンパイラーやインタプリタを作った経験がある人ならば、Interpreterパターンじゃないの?と思われるかもしれませんが、構文解析木をちゃんと作ってみたくて、こだわっています。

Compositeパターンとは

そもそものデザインパターンについては、どこかのページで詳細をまとめているので丁寧に繰り返すことはしませんが、オブジェクト指向で再利用性を高く設計するため、よく現れる問題とそれに対処する設計を整理し23のパターンとしてまとめられたものです。

そのうち、今回はCompositeパターンを取り上げます。

イメージとしては、コンピュータ内でのファイルの保存をイメージしてもらうとよいと思います。

ルートディレクトリの中には、ファイルがあったりサブディレクトリがあったり、さらにサブディレクトリの中にもファイルがあったりさらに下の階層のサブディレクトリがあったりします。

しかし、ファイルの中にファイルやサブディレクトリを入れることはできません。

ディレクトリの中身として、ファイルまたはサブディレクトリが入れられる階層構造になっています。

また、ファイルやディレクトリのサイズを考えたとき、ファイルについてはファイルそのもののサイズになります。

一方、ディレクトリはサブディレクトリ内のファイルやさらに下位のサブディレクトリの中身のサイズの合計がディレクトリのサイズになります。

ファイルとディレクトリでは、同じサイズといっても求め方が異なります。

このような違いを乗り越えて、とにかく「サイズ!」と一括りに扱うことができると区別をしなくて済むのでとても便利になります。

このようなファイルやディレクトリのような関係を扱えるように、オブジェクト指向で実現するデザインパターンがCompositeパターンになります。

Compositeパターンのクラス図

今回、実装を試してみるCompositeパターンのクラス図は次になります。

Compositeパターン

leafクラスはファイルにあたり、中に他のleafや次に出てくるnodeなどを入れることはできません。

nodeクラスはディレクトリにあたり、中にleafクラスのインスタンスやnodeクラスのインスタンスを入れることができます。

これらを区別して、「leafやnode」のように呼ぶのではなく、まとめてentryと呼んでしまおうという感覚です。

言い換えれば、中身と容器を同一視してしまおうといったところでしょうか。

一応メンバ変数とメソッドについて簡単に説明しておきます。

変数_typeは、leafなのかnodeなのかがわかるように、どちらのクラスかを記憶しておくものになります。

leafクラスの場合には"L"、nodeクラスの場合には"N"としておきます。(getTypeは実装していませんが・・・)

変数_elementは、要素になります。

leafクラスでは文字列です。ファイルの中身と思ってください。

nodeクラスではentryクラスのリストです。ポインタのリストにしているのは、toStringメソッドが純粋仮想関数にしてあり、実装は子クラスに任せているためなのか、ポインタにしないとエラーになってしまったからです。

setメソッドは、_elementに入れる感じです。(leafでは代入、nodeではリストに追加)

getTypeメソッドは変数_typeを、toStringメソッドは文字列に変換した値を戻します。

実装したプログラム

それでは実装してみます。

クラスの定義です。これは、entry.hという名前のファイルとしておきます。

#pragma once
#include<string>
#include<list>
#include<iostream>
using namespace std;

class entry
{
public:
    virtual string toString() = 0;
    string getType() {
        return _type;
    }
protected:
    string _type;
};

class leaf :public entry
{
public:
    void set(string name) {
        _type = "L";
        _element = name;
    }

    string toString() override {
        return '(' + _element + ')';

    }
private:
    string _element;
};

class node :public entry
{
public:
    void set(entry *e) {
        _type = "N";
        _element.push_back(e);
    }
    
    string toString() {
        string res;
        for(auto e :_element)
        {
            res += e->toString();
            if (e != _element.back()) {
                res += ',';
            }
        }
        return '[' + res + ']';
    }
private:
    list<entry*> _element;
};

次にこれらのクラスを実際に使ってみるプログラムです。

#include"entry.h"
#include <iostream>
using namespace std;

int main()
{
    leaf l1, l2, l3, l4;
    l1.set("A");
    l2.set("B");
    l3.set("C");
    l4.set("D");

    node n1, n2, n3;
    n1.set(&l1);

    n2.set(&l2);
    n2.set(&n1);
    n2.set(&l3);

    n3.set(&n2);
    n3.set(&l4);

    cout << n3.toString() << endl;
    cout << "Type n3 : " << n3.getType() << endl;
    cout << "Type l3 : " << l3.getType() << endl;
}

それでは実行してみます。

[[(B),[(A)],(C)],(D)]
Type n3 : N
Type l3 : L

leafは丸括弧で、nodeは角括弧でくくってみました。

今回はこれでおしまいにします。

それではまた。

Posted by 春日井 優