Compositeパターンを書いてみた
まだまだ設計やデザインパターン、それらに使う継承などに弱いと思う。 今回はcompositeパターン?を書いてみた。 (ずっとcomponentパターンだと思ってたけど違う?) MVCが自力で書けるようになりたいね。
ファイルなどの階層構造をクラスの階層構造で表現する。
検索で出てくるコードには、個人的には「アンチパターンじゃないのか?」という書き方があったりして、納得がいかなかったので書いてみた。
時間はかかったが、個人的には木構造や双方向リストのようなものが作れるわりにはnull pointerのエラーが少なくて済むように思う。コンテナ使っているのも大きい。
どちらかというとコードも追いやすいかもしれない。
楽しくなってdirectoryの関数を増やしすぎてしまったので、すでにサンプルには向かない気がしているけれど折角書いたので。
共通項については親/基底である抽象クラス/インターフェイスが持つのは問題ない。 まとめて基底型として管理して、ある時には任意の派生型のみを取り扱いたい、なんてことがきっとあるだろうけど、どうやるのが良い方法なのかよくわからない。 とりあえずざっくり2通りだろう。
- 派生型が持つメソッドはすべて基底型も持つ
- 基底型が持つのは最低限の共通部分だけで、派生型それぞれが必要な分だけメソッドを持つ
- 基底型とすべての派生型が同じメソッドのみを持つ
(1. )は良くないだろう。基底型はすべての派生型を抽象的に表現したものだろうから、どちらかというと派生型を基底型の枠にはめ込むほうが良いはずだ。 だから(2.)が望ましいと思う。(3.)は継承を使わずに、すべて同じ型で良いだろう。 (2.)をベースになるべくシンプルに(1.)から遠ざけて、(3.)に近づけるつもりで考えればいいのか。。。?
基底型は派生型がどういうものかは知らないが、基底型自身がうまく定義されている限りは派生型であればそれなりにどれでも、うまく取り扱うことができるはずだ。そう期待していいはずだ。
しかし、例えば今回のコードでは基底クラスがis_dirとis_fileの 2つの関数を持っている。
これはある意味、基底が派生型を知っていることになってしまうのできっと良くないやり方だ。
(もし第3の派生型が生まれたら、基底クラスを書き換える必要が出るんじゃないか?)
一方で、componentから派生した型であればそれが何かを調べることができる、というのもある意味では共通した性質に思えるので、どうにか比較する方法が基底型からサポートされていても良さそうな気がする。ただ、錯覚というか、これは間違っている気がする。
基底型からするとどんな派生型も基底型と同一であって区別するとかしないとかそういう話じゃない、という考え方をしたほうがいいのだろうか。
show関数は仮想関数にして派生型ごとに書き換えるのにベストな関数なんだろうなぁとは思う。
Java、C#のToString、pythonのstrに近いものだろうし、型によって中に持っているデータの量や種類、重要度は異なるだろうから。
もともと古いパターンの1つであって、今なお素晴らしいといえるものかどうかは分からないので、そもそもこのデザインパターンが良いのかはわからない気がしてきた。 継承無しでdirectoryの再帰+directoryがfile(再帰しない要素)の配列を持つような実装なら継承いらないんだよなぁ・・・
やはり継承はうまく使うのが難しい。
//main.cpp #include<string> #include<iostream> #include<list> #include<memory> #include<queue> #include<map> //#include<optional> class directory; class file; class component { std::string name_; int depth_ = 0; const component* parent_ptr; protected: auto& parent(const component* new_ptr ){ parent_ptr = new_ptr; return *this; } auto& depth( int new_depth ){ depth_ = new_depth; return *this; } public: component( const std::string&name_v ,int depth_v,const component*parent_ptr_v = nullptr) :name_{ name_v } , depth_{ depth_v} , parent_ptr{parent_ptr_v} {} auto depth()const{ return depth_; } auto parent()const{ return parent_ptr; } auto& rename( const std::string& new_name_ ){ name_ = new_name_; return *this; } auto name()const{ return name_; } virtual auto is_file()const->bool = 0; auto is_dir()const->bool{ return !this->is_file(); } auto show()const->void{ std::cout << ( parent() == nullptr ? "None" :parent()->name() ) << "\t" << name() << "\t" << depth() << "\t" << (is_dir() ? "Dir" : "File" ) << "\n"; } auto show_format()const{ std::cout << "parent" << "\t" << "name" << "\t" << "depth" << "\t" << "dir/file\n"; } virtual ~component() = default; }; class file final :public component { using super = component; std::string file_type_; public: file( const std::string& name_, int depth_v , const component* parent_ptr_v, const std::string& file_type_v ) :super{ name_,depth_v,parent_ptr_v }, file_type_{ file_type_v } {} auto is_file()const->bool override{ return true; } }; class directory final:public component { using super = component; std::map<std::string,std::unique_ptr<component>> children; protected: template<class T, class ...Args> auto& add( const std::string&name, Args&&...args ){ auto t = std::make_unique<T>( name , depth()+1 ,static_cast<const directory*>(this) ,std::forward<Args>( args )... ); children.emplace(name, std::move(t) ); return *this; } public: directory( const std::string& name_, int depth_v,const component*parent_ptr_v = nullptr ) :super{ name_,depth_v,parent_ptr_v } {} auto size()const{ return children.size(); } /* auto& add( const component& comp ){ children.emplace_back( comp ); return *this; }*/ auto is_file()const->bool{ return false; } auto& add_file( const std::string& name_, const std::string& file_type_){ return add<file>( name_, file_type_ ); } auto& add_dir(const std::string& name_ ){ return add<directory>( name_); } auto& as_map()&{ return this->children; } auto& as_map()const&{ return this->children; } template<class F> auto as_map_if( F&&f )const&{ std::map<std::string, component*> t; for( const auto&e : children ) { if( f(*e.second) ) t[e.first] = e.second.get() ; } return t; } auto files()const&{ auto&&t = as_map_if( []( auto&x ) { return x.is_file(); } ); std::map<std::string, file*> ret; for( auto&&e : t ) ret[e.first] = std::move( dynamic_cast<file*>( e.second ) ); return ret; } auto dirs()const&{ auto&& t = as_map_if( []( auto&x ) { return x.is_dir(); } ); std::map<std::string, directory*> ret; for( auto&&e : t ) ret[e.first] = std::move( dynamic_cast<directory*>( e.second ) ); return ret; } /* after C++ 17 auto find( const std::string& name )const{ auto it = children.find( name ) ; return it != std::end( children ) ? std::make_optional( *it ) : std::nullopt; } */ auto find( const std::string& name )const{ auto it = children.find( name ) ; return it != std::end( children ) ? it->second.get() : nullptr; } auto find_dir( const std::string& name )const{ auto ptr = find( name ); return ptr != nullptr && ptr->is_dir() ? static_cast<directory*>(ptr) : nullptr ; } auto find_file( const std::string& name )const{ auto ptr = find( name ); return ptr != nullptr && ptr->is_dir() ? static_cast<file*>( ptr ) : nullptr; } auto& print_bfs()const{ std::cout << "___BFS___\n"; show_format(); std::queue<const component*> que; que.emplace(this); while( !que.empty() ) { // std::cout <<"DEBUG:"<< que.size() << "\n"; auto&&here = que.front(); que.pop(); here->show(); if( here->is_dir() ) { auto&& mp = dynamic_cast<const directory*>( here )->as_map(); for( auto&child : mp ) que.emplace( child.second.get() ); } } std::cout << "___BFS___\n"; return *this; } auto& print_dfs()const{ std::cout << "___DFS___\n"; auto& p = print_dfs_impl(); std::cout << "___DFS___\n"; return p; } template<class F> auto map_dfs( F&&f )->void{ f( this ); for( auto&e : this->children ) { auto ptr = e.second.get(); if( ptr->is_dir() ) dynamic_cast<directory*>( ptr )->map_dfs( f ); else f( ptr ); } } private: auto print_dfs_impl()const->const directory&{ this->show(); for(const auto&child :this->children ) { //child.second->show(); if( child.second->is_dir() ) static_cast<directory*>( child.second.get() )->print_dfs_impl(); else child.second->show(); } return *this; } }; auto check( component* cp){ std::cout << "component : " << cp->name()<<"\n"; } auto check( directory* dp ){ std::cout << "directory : " << dp->name()<<"\n"; } auto check( file* fp ){ std::cout << "file : " << fp->name() << "\n"; } int main(){ directory root{ "root",0 }; root.add_dir( "user" ).add_dir("program"); // if( root.find_dir( "user" ) == nullptr ) // std::cout << "none\n"; root.find_dir( "user" ) ->add_dir( "Alice" ) .add_dir( "Bob" ) .add_dir( "Cris" ); root.find_dir( "program" ) ->add_dir( "mine" ) .add_dir( "text" ); root .find_dir( "user" ) ->find_dir( "Alice" ) ->add_file( "diary","txt" ) .add_file( "memento","image" ); /* for( auto&&e : root.as_map() ) { std::cout << e.first << " "; e.second->show();// << "\n"; } */ root.print_bfs(); std::cout << "\n"; root.print_dfs(); std::cout << "\n"; root.map_dfs( []( auto&&x ) { check( x ); } ); return 0; }
//console.log ___BFS___ parent name depth dir/file None root 0 Dir root program 1 Dir root user 1 Dir program mine 2 Dir program text 2 Dir user Alice 2 Dir user Bob 2 Dir user Cris 2 Dir Alice diary 3 File Alice memento 3 File ___BFS___ ___DFS___ None root 0 Dir root program 1 Dir program mine 2 Dir program text 2 Dir root user 1 Dir user Alice 2 Dir Alice diary 3 File Alice memento 3 File user Bob 2 Dir user Cris 2 Dir ___DFS___ directory : root directory : program directory : mine directory : text directory : user directory : Alice component : diary component : memento directory : Bob directory : Cris