很不幸的這些編碼也流竄到產品的程式碼中,當年小弟只能照虎畫貓,最近終於找到一種比較好的作法,相信很多人也被這樣的問題困擾,讓我們繼續往下看。
依據產品 ID 產生物件
相信您要不是見過就是寫過類似下方的程式碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | #include "product_a.h" #include "product_b.h" #include "product_c.h" #include "product_factory.h" Product* ProductFactory( int id) { Product* product; switch (id) { case PRODUCT_A_ID: product = new ProductA; break ; case PRODUCT_B_ID: product = new ProductB; break ; case PRODUCT_C_ID: product = new ProductC; break ; default : product = 0; } return product; } |
程式碼就是我們的小孩,我們不能讓管理中心製造的各種神秘編碼污染小孩的 DNA。解決辦法就是建立一道防火牆,需要認識編碼的只有該物件本身,並且不使用 switch case id 產生物件,而由物件自己施展影分身之術:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 | //class Product(interface class) class Product { public : virtual Product(){} int id() const =0; Product* clone() const =0; }; //class Product A class ProductA : public Product { public : Product(); virtual ~Product(); int id() const =0; ProductA* clone() const =0; }; //... ProductA::ProductA(){} ProductA::~ProductA(){} int ProductA::id() const { return PRODUCT_A_ID; } ProductA* ProductA::clone() { return new ProductA; } //class Product B class ProductB : public Product { public : Product(); virtual ~Product(); int id() const =0; ProductB* clone() const =0; }; //... ProductB::ProductB(){} ProductB::~ProductB(){} int ProductB::id() const { return PRODUCT_B_ID; } ProductB* ProductB::clone() { return new ProductB; } |
現在還剩下一個問題就是何處呼叫這些 clone(),最好的辦法就是建立一個工廠把自己註冊進去,工廠就知道要如何生產產品了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 | //product_factory.h #include <map> class Product; class ProductFactory { ProductFactory(); public : ~ProductFactory(){} void registerProduct(Product*); Product* create( int id); static ProductFactory* instance(); private : typedef std::map< int > Map; Map map_; static ProductFactory *inst_; }; //product_factory.cpp #include "product.h" #include "product_factory.h" ProductFactory* ProductFactory::inst_ = 0; ProductFactory* ProductFactory::instance() { if (!inst_) inst_ = new ProductFactory; return inst_; } void ProductFactory::registerProduct(Product *p) { if (map_.count(p->id())) return ; map_[p->id] = p; } Product* ProductFactory::create( int id) { Map::iterator it = map_.find(id); if (it != ma_.end()) return it->second->clone(); else return 0; } //product_a.cpp(class ProductA) static ProductA *RegisterProductA = new ProductA; ProductA::ProductA() { ProductFactory::instance()->registerProduct( this ); } |
而 ProductFactory::instance() 已在前面的文章分享過,這邊不再重複。
改善 State Pattern 設計
這邊就不對 State Pattern 做詳細介紹了,請自行參照四人幫第五章,由上圖可以看出 ConcreteStateA、ConcreteStateB...會依據狀態的複雜度而不斷增長。麻煩的是不同狀態間的轉換,小弟試過不少辦法包含很少人用的 RTTI,後來發現還是簡單的 enum 最好用,用列舉值作為 state id 切換狀態,但這些 id 要怎麼對應到 ConcreteStateA、ConcreteStateB...?
早些年免不了使用 switch case,現在我們有了 Prototype Pattern 日子可以輕鬆一點了,不需在 Context 放進一大堆 header files(ConcreteStateXXX)還有 switch case。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | //ConcreateStateA implementation static ConcreateStateA* RegisterConcreateStateA = new ConcreateStateA ConcreateStateA::ConcreateStateA() { StateFactory::instance()->add( this ); } ConcreateStateA* ConcreateStateA::clone() { return new ConcreateStateA; } StateId ConcreateStateA::id() const () { return kConcreateStateA; } //ConcreateStateB implementation static ConcreateStateB* RegisterConcreateStateB = new ConcreateStateB; ConcreateStateB::ConcreateStateA() { StateFactory::instance()->add( this ); } ConcreateStateB* ConcreateStateA::clone() { return new ConcreateStateA; } StateId ConcreateStateB::id() const () { return kConcreateStateA; } |
結語
在重構一書中曾經說過 OOP 很少使用 switch case,最近才有了些許的領悟,在此分享給各位,謝謝大家~
參考資料: C++ Gotchas: Avoiding Common Problems in Coding and Design
參考資料: C++ Gotchas: Avoiding Common Problems in Coding and Design
沒有留言:
張貼留言