65.9K
CodeProject 正在变化。 阅读更多。
Home

用于隔离遍历(基于标准)和操作的专用访问者设计

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.67/5 (3投票s)

2012 年 12 月 27 日

CPOL

3分钟阅读

viewsIcon

13263

downloadIcon

100

通过隔离遍历和操作来隐藏内部数据结构的复杂性

引言

现实生活中的软件应用程序经常会陷入一种糟糕的数据结构选择阻碍其可扩展性的状态。 这种设计会将大量的内部职责推给客户端。 这使得系统更加脆弱,并为错误、代码重复导致性能不佳和不稳定提供了许多原因。 虽然理想的解决方案是修复根本原因并进行重大更改或重写,但更多时候,您无法这样做。 除非您将其完全隐藏在外部世界之外,否则核心数据结构很少会发生变化。

在这里,我将展示一个基于一种常见设计模式设计的应用程序的示例。 当时它可以满足它的目的,但后来随着需求的出现,当前的设计无法承受它。

建议采用一种改进的设计来解决同样的问题。

背景

让我们考虑上面的类图。 这是一个典型的银行Transaction类层次结构。 银行每天进行数百万笔这样的交易。 维护这些交易的历史/记录是其主要任务之一。 让我们假设有另一个类“TransactionLog”,如下所示。

这包含数据结构“_transactions: TransactionLogDataSource”,其中存储/持久化所有交易详细信息。 不幸的是,这个DS没有经过充分的设计,程序员将其实现如下

typedef map<string, map<string, Transaction* > > TransactionLogDataSource;

即,map<account_id, map<date_of_txn, Transaction*> 这基本上是以account_id作为键的map,而值是另一个以交易日期作为键,以Transaction指针作为值的map。 这就是我所说的糟糕的数据结构选择!!!

但它的复杂性并不是一个大问题,因为设计者选择应用“访问者设计”模式来提供诸如:总提款金额?

客户端代码

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

遍历逻辑和对访问者的操作(算法)的调用

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;
    }
}

输出

问题

目前为止,一切都很好!

过一段时间后,出现了一个新的业务需求,即报告每日提款金额,即银行中每个帐户的每日提款金额列表。 现在,事情变得复杂了,因为

TransactionLog::accept1(TrasactionVisitor* txnVisitor) 

必须修改“accept1(...)”逻辑以考虑日期,或者在“TransactionLog”中添加一个新版本的“accept(...)”方法。 所以基本上,对于每个新的报告标准,都需要一个新版本的“accept(...)”。 如果您仔细观察,这都是因为遍历标准发生了变化,而不是遍历逻辑或操作。

因此,当务之急是分离遍历和操作,以便无论遍历标准是什么,遍历逻辑和操作(算法)都保持不变。

解决方案

让我们让另一个访问者负责遍历策略,如下所示

专门的遍历策略访问者获取TransactionLogDataSource,应用过滤条件(日期)并调用操作访问者。

客户端代码

构建用于按日期报告的专用遍历策略访问者

TraversalStrategyVisitor_DateBased(string date) : _date(date) {}     

TransactionLog::getInstance()->accept() 还有一个额外的参数 TraversalStrategyVisitor_DateBased*,用于执行基于标准的遍历,然后允许访问者访问Transaction*进行操作。

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

它的遍历逻辑和对访问者的操作(算法)的调用

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

通过上述方法,...

void TransactionLog::accept(TraversalStrategyVisitor* trvslVisitor,  TrasactionVisitor* txnVisitor)

...保持完整,即核心框架类中没有变化。

假设需要另一个报告标准,您需要做的就是引入新的专门的遍历策略访问者类及其遍历实现。 其他一切保持不变。 此外,如果您决定为了自己的利益而更改内部数据结构,则客户端无需进行任何更改。 好吧,这就是访问者设计模式的第一个收获,即使背景中解释的方法也支持它。

完整的客户端代码

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

输出

历史

  • 2012年12月27日:首次发布
© . All rights reserved.