visitor模式
2015-06-18 17:27
302 查看
Specialized visitor design for segregating traversal (criteria based) and operation.
Introduction
Software applications in real life often gets into a shape where a bad Data Structure choice hinders its scalability. Such designs push lots of internal responsibility to client side. This makes the system more vulnerable and provides many reasons for errors,code duplicity leading to poor performance and instability. While the ideal solution is to fix the root cause and go for big change or re-write but more often than you’d not be in position to do the same. The core data structures should seldom be subject to
change, unless you have kept it completely hidden from the outer world.
Here I am going to present an example where an application was designed based one common design pattern. It sufficed its purpose then but later as requirements came in the current design was not able to hold it through.
An improvised design is suggested as a solution to address the same.
Background
Let's take above class diagram in consideration. This is a typical Bank's Transaction class hierarchy. Bank's do millions of such transaction on a any given day. Maintaining the history/records of these transaction is one of it's primary task. Lets assume
there is an another class 'TransactionLog' as below for the same.
This contains the Data Structure '_transactions: TransactionLogDataSource' where all the transaction details are stored/persisted. Unfortunately this DS was not thought fully designed and the coder
implemented it as following:-
typedef map<string, map<string, Transaction* > > TransactionLogDataSource;
I.e., map<account_id, map<date_of_txn, Transaction*> this basically is map with account_id as key and value is an another map with date of transaction as key and Transaction pointer as its value. This is what I meant with bad Data Structure choice!!!
But its complexity was not of a huge concern because designer chose to apply 'Visitor Design' Pattern for providing solution like: Total withdraw-ed amount?
Client code:
Hide Copy Code
Account* acc1 = new Account(); acc1->setAccountNum("001"); acc1->setBalance(40000.09); acc1->setPin(1234); WithDrawal* wd1 = new WithDrawal(); wd1->setFromAccount(acc1); wd1->setDate("2012"); wd1->setAmount(1070.00); WithDrawal* wd2 = new WithDrawal(); wd2->setFromAccount(acc1); wd2->setDate("2011"); wd2->setAmount(167000.00); WithDrawal* wd3 = new WithDrawal(); wd3->setFromAccount(acc1); wd3->setDate("2010"); wd3->setAmount(104500.00); TransactionLog::getInstance()->addTransaction(wd1); TransactionLog::getInstance()->addTransaction(wd3); TransactionLog::getInstance()->addTransaction(wd4); WithdrawalTransactionVisitor* wtVisitorX = new WithdrawalTransactionVisitor(); TransactionLog::getInstance()->accept1(wtVisitorX); wtVisitorX->printCountAndAmount();
Traversal Logic and call to visitor for operation (algorithm):
Hide Copy Code
void TransactionLog::accept1(TrasactionVisitor* txnVisitor) { cout << "Size of DS: " << _transactions.size() << endl; for (TransactionLogDataSource::iterator i=_transactions.begin(); i != _transactions.end(); ++i) { map<string, Transaction* > m = (*i).second; for (map<string, Transaction*>::iterator j=m.begin(); j != m.end(); ++j) { Transaction* txn = (*j).second; txnVisitor->visit(txn); } } } virtual void WithdrawalTransactionVisitor::visit(Transaction* txn) { if (txn->getType() == 1) { WithDrawal *wd = dynamic_cast<WithDrawal*>(txn); if(wd != NULL) _totalAmount += wd->getAmount(); ++_count; } }
Output:
Problem
So far, So good!Sometime down the line, a new business requirement comes to report the daily withdrawal amount i.e. day wise list of withdrawn amount for each account in the bank. Now, this is where the things gets complicated because
Hide Copy Code
TransactionLog::accept1(TrasactionVisitor* txnVisitor)
'accept1(...)' logic will have to be modified to take dates into consideration or add a new version of 'accept(...)' method in 'TransactionLog' for the same. So basically with every new reporting criteria a new version of 'accept(...)' will be required.
And if you see to it, this is all because the traversing criteria has changed not the traversal logic or operation.
Thus the need of hour is to segregate the traversal and operation so that whatever be the traversal criteria, the traversal logic and operation (algorithm) stays untouched.
Solution
Let's have another visitor responsible for traversal strategy as following:-The specialized traversal strategy visitor takes the
TransactionLogDataSource, applies the filter criteria (dates)
and calls the operations visitor.
Client Code:
Construction of specialized traversal strategy visitor for date wise reporting:Hide Copy Code
TraversalStrategyVisitor_DateBased(string date) : _date(date) {}
There is an additional parameter
TraversalStrategyVisitor_DateBased*to
TransactionLog::getInstance()->accept(),
to do the criteria based traversing and then allow visitor to visit the
Transaction*for operation.
Hide Copy Code
WithdrawalTransactionVisitor* wtVisitor = new WithdrawalTransactionVisitor(); TraversalStrategyVisitor_DateBased* tvslVistorDtStrategy = new TraversalStrategyVisitor_DateBased("2012"); TransactionLog::getInstance()->accept(tvslVistorDtStrategy, wtVisitor); wtVisitor->printCountAndAmount(); void TransactionLog::accept(TraversalStrategyVisitor* trvslVisitor, TrasactionVisitor* txnVisitor) { trvslVisitor->traverse(_transactions, txnVisitor); }
Its traversal logic and call to visitor for operation (algorithm):
Hide Copy Code
virtual void TraversalStrategyVisitor_DateBased::traverse( TransactionLogDataSource _transactions, TrasactionVisitor* txnVisitor) { for (TransactionLogDataSource::iterator i=_transactions.begin(); i != _transactions.end(); ++i) { map<string, Transaction* > m = (*i).second; for (map<string, Transaction* >::iterator j=m.begin(); j != m.end(); ++j) { Transaction* txn = (*j).second; if (_date == txn->getDate()) txnVisitor->visit(txn); } } }
With the above approach the
Hide Copy Code
void TransactionLog::accept(TraversalStrategyVisitor* trvslVisitor, TrasactionVisitor* txnVisitor)
remains intact i.e. no change in the core-framework classes.
Suppose there comes the need of another reporting criteria, all you need to do is to introduce new specialized traversal strategy visitor class and its implementation for traversal. Everything else remains untouched. Moreover in case you decide the change
the internal data structure for your own good there will be no changes required in the client side. Well, that's the first take away of Visitor Design Pattern and even approach explained in background supported it.
Complete Client Code
Hide ShrinkCopy Code
Account* acc1 = new Account(); acc1->setAccountNum("001"); acc1->setBalance(40000.09); acc1->setPin(1234); Account* acc2 = new Account(); acc2->setAccountNum("002"); acc2->setBalance(40000.09); acc2->setPin(1234); Account* acc3 = new Account(); acc3->setAccountNum("003"); acc3->setBalance(40000.09); acc3->setPin(1234); WithDrawal* wd1 = new WithDrawal(); wd1->setFromAccount(acc1); wd1->setDate("2012"); wd1->setAmount(1070.00); WithDrawal* wd2 = new WithDrawal(); wd2->setFromAccount(acc1); wd2->setDate("2011"); wd2->setAmount(167000.00); WithDrawal* wd3 = new WithDrawal(); wd3->setFromAccount(acc1); wd3->setDate("2010"); wd3->setAmount(104500.00); WithDrawal* wd4 = new WithDrawal(); wd4->setFromAccount(acc2); wd4->setDate("2012"); wd4->setAmount(7000.00); WithDrawal* wd5 = new WithDrawal(); wd5->setFromAccount(acc2); wd5->setDate("2011"); wd5->setAmount(167000.00); WithDrawal* wd6 = new WithDrawal(); wd6->setFromAccount(acc2); wd6->setDate("2010"); wd6->setAmount(104500.00); WithDrawal* wd7 = new WithDrawal(); wd7->setFromAccount(acc3); wd7->setDate("2012"); wd7->setAmount(5000.00); WithDrawal* wd8 = new WithDrawal(); wd8->setFromAccount(acc3); wd8->setDate("2011"); wd8->setAmount(167000.00); WithDrawal* wd9 = new WithDrawal(); wd9->setFromAccount(acc3); wd9->setDate("2010"); wd9->setAmount(104500.00); TransactionLog::getInstance()->addTransaction(wd1); TransactionLog::getInstance()->addTransaction(wd2); TransactionLog::getInstance()->addTransaction(wd3); TransactionLog::getInstance()->addTransaction(wd4); TransactionLog::getInstance()->addTransaction(wd5); TransactionLog::getInstance()->addTransaction(wd6); TransactionLog::getInstance()->addTransaction(wd7); TransactionLog::getInstance()->addTransaction(wd8); TransactionLog::getInstance()->addTransaction(wd9); std::cout << endl << endl << std::endl; WithdrawalTransactionVisitor* wtVisitor = new WithdrawalTransactionVisitor(); TraversalStrategyVisitor_DateBased* tvslVistorDtStrategy = new TraversalStrategyVisitor_DateBased("2012"); TransactionLog::getInstance()->accept(tvslVistorDtStrategy, wtVisitor); cout << "For the date: 2012" << std::endl; wtVisitor->printCountAndAmount(); std::cout << endl << endl << std::endl; WithdrawalTransactionVisitor* wtVisitor1 = new WithdrawalTransactionVisitor(); TraversalStrategyVisitor_DateBased* tvslVistorDtStrategy1 = new TraversalStrategyVisitor_DateBased("2011"); TransactionLog::getInstance()->accept(tvslVistorDtStrategy1, wtVisitor1); cout << "For the date: 2011" << std::endl; wtVisitor1->printCountAndAmount(); std::cout << endl << endl << std::endl; WithdrawalTransactionVisitor* wtVisitor2 = new WithdrawalTransactionVisitor(); TraversalStrategyVisitor_DateBased* tvslVistorDtStrategy2 = new TraversalStrategyVisitor_DateBased("2010"); TransactionLog::getInstance()->accept(tvslVistorDtStrategy2, wtVisitor2); cout << "For the date: 2010" << std::endl; wtVisitor2->printCountAndAmount();
相关文章推荐
- I CONTROL [initandlisten] ** WARNING: /sys/kernel/mm/transparent_hugepage/enabled is 'always'.
- D-Soldier and Number Game(CF546D) Codeforces Round #304 (Div. 2)
- 数据结构上机实验之二分查找 分类: 查找 2015-06-18 17:27 9人阅读 评论(0) 收藏
- [翻译][MVC 5 + EF 6] 5:Code First数据库迁移与程序部署
- 团队项目第二次冲刺(4)
- [LintCode] Longest Increasing Continuous subsequence II
- Oracle 11g XE 是 Oracle 数据库的免费版本
- codeblock中 启动 ROS 节点
- 元组
- SQL*LOADER的使用总结
- Oracle 11g 上安装ASM(RHEL5)
- 【弱省胡策】Round #5 Handle 解题报告
- android 绘制椭圆 圆角矩形 详细解析
- [MFC]传统控件:控件简介以及CButton按钮类
- 【转】SQLite提示database disk image is malformed的解决方法
- 【弱省胡策】Round #5 Handle 解题报告
- 事件的传递(一篇老外的博客)
- obj-c编程19:关联对象
- obj-c编程19:关联对象
- SQL Server的update语句的工作原理