Factory&Builderパターン再考
インスタンスの生成方法について再考。
したいこと
- 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; }