読者です 読者をやめる 読者になる 読者になる

Factory&Builderパターン再考

c++

インスタンスの生成方法について再考。


したいこと

  • Dataの商品IDに応じて、違うルールでShelfを作りたい。
  • 新しい商品を追加も容易にしたい。追加や変更するクラス、箇所を極力小さくしたい。
int main()
{
    ProductId("c1").procedure<Screw1Builder, BoardBuilder, ShelfBuilder>();
    ProductId("c2").procedure<Screw2Builder, BoardBuilder, ShelfBuilder>();

    Shelf s1 = ShelfFactory::create(Data("c1"));
    Shelf s2 = ShelfFactory::create(Data("c2"));

    return 0;
}

上記では、IDによって違うScrewを使うように実行時にfactoryに登録している。
procedureは可変長引数となっており、手順を増やすことも容易。

ShelfFactoryはstd::functionを保持しているので、Screw1Builderなどに依存していない。
Screw1Builder等も同じBaseを継承する必要もない。

class Screw {};
class Board {};
class Shelf {};

struct WoreHouse {
    Screw screw;
    Board bord;
    Shelf shelf;
};


class ShelfFactory {
public:
    class ProcedureCreator {
        friend ShelfFactory;
    public:
        ProcedureCreator& next(const std::function<void(ShelfFactory&)>& f)
        {
            _p.push_back(f);
            return *this;
        }
    private:
        ProcedureCreator(std::list<std::function<void(ShelfFactory&)>>& p)
            : _p(p)
        {}
        std::list<std::function<void(ShelfFactory&)>>& _p;
    };


    static ProcedureCreator makeProcedure(const std::string& code)
    {
        instance()._procedures[code].clear();
        return ProcedureCreator(instance()._procedures[code]);
    }
    static Shelf create(const Data& d)
    {
        ShelfFactory& f = instance();
        f._data = d;
        for (auto& p : f._procedures[f._data.id]) p(f);
        return f._worehous.shelf;

    }
    Data _data;
    WoreHouse _worehous;
    std::map<std::string, std::list<std::function<void(ShelfFactory&)>>> _procedures;
private:
    static ShelfFactory& instance() {
        static ShelfFactory _f;
        return _f;
    }
    ShelfFactory() {}
    ShelfFactory(const ShelfFactory&) = default;
    ShelfFactory& operator =(const ShelfFactory&) = default;
};

class Screw1Builder {
public:
    void operator()(ShelfFactory& f)
    {
        std::cout << "Screw1Builder" << std::endl;
        f._worehous.screw = Screw();
    }
};

class Screw2Builder {
public:
    void operator()(ShelfFactory& f)
    {
        std::cout << "Screw2Builder" << std::endl;
        f._worehous.screw = Screw();
    }
};

class BoardBuilder {
public:
    void operator()(ShelfFactory& f)
    {
        std::cout << "BoardBuilder" << std::endl;
        f._worehous.bord = Board();
    }
};

class ShelfBuilder {
public:
    void operator()(ShelfFactory& f)
    {
        std::cout << "ShelfBuilder" << std::endl;
        f._worehous.shelf = Shelf();
    }
};


template <typename...>
struct ProcedureMaker {
    static void apply(ShelfFactory::ProcedureCreator& p) {
    }
};

template <typename T, typename... Args>
struct ProcedureMaker<T, Args...> {
    static void apply(ShelfFactory::ProcedureCreator& p) {
        p.next(T());
        ProcedureMaker<Args...>::apply(p);
    }
};

class ProductId {
public:
    ProductId(std::string id) : _id(id) {}
    template <typename ... Args>
    void procedure() const
    {
        ProcedureMaker<Args...>::apply(ShelfFactory::makeProcedure(_id));
    }
private:
    std::string _id;
};


新しい工程を増やす場合、新しい部品(Glass)とその作り方(GlassBuilder)を新たに作成。
WoreHouseに新しい部品のメンバ変数を追加。
あとはmainで製造手順を追加すればよい。

変更箇所の影響がかなり限定的にできる。

class Glass {};

struct WoreHouse {
    Screw screw;
    Board bord;
    Shelf shelf;
    Glass glass; //追加
};

class GlassBuilder {
public:
    void operator()(ShelfFactory& f)
    {
        std::cout << "GlassBuilder" << std::endl;
        f._worehous.glass = Glass();
    }
};


int main()
{
    ProductId("c3").procedure<GlassBuilder, Screw2Builder, BoardBuilder, ShelfBuilder>();
    Shelf s3 = ShelfFactory::create(Data("c3"));

    return 0;
}