Click here to Skip to main content
Click here to Skip to main content

Specialized visitor design for segregating traversal (criteria based) and operation.

, 27 Dec 2012 CPOL
Rate this:
Please Sign up or sign in to vote.
Hide the internal data structure's complexity by segregating traversal and operations.

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:

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):

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

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:

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.

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):

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

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

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();

Output:

History

Initial release. 

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Mitendra Anand
Architect
India India
My name is Mitendra Anand and my work is focused around application development which includes a lot of prototyping of new solutions.

While I have a background in C++/VC++ programming, my daily work is mostly spent in C++, Sybase, SQL, Unix/Windows.
Follow on   Twitter

Comments and Discussions

 
QuestionYour example visitor pattern is not the visitor pattern I know... PinmemberAndreas Gieriet27-Dec-12 13:50 
AnswerRe: Your example visitor pattern is not the visitor pattern I know... PinmemberMitendra Anand28-Dec-12 2:47 
GeneralRe: Your example visitor pattern is not the visitor pattern I know... PinmemberAndreas Gieriet28-Dec-12 5:03 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web01 | 2.8.141223.1 | Last Updated 27 Dec 2012
Article Copyright 2012 by Mitendra Anand
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid