C# - 当一个对象改变状态时,是否应该负责创建一个历史对象?

时间:2010-07-13 16:21:14

标签: c# architecture oop poco chain-of-responsibility

这是一个架构/最佳实践问题,而不是其他任何问题,所以请随意添加两美分。我知道我在标题中声明了状态,但这适用于对象的任何基本属性。我认为下面的帐户示例将有助于证明我的问题比状态好一点。

以下是一个示例帐户对象:

public class Account
{
   private IList<Transaction> _transactions;

   public AddTransaction(trans as Transaction)
   {
      _transaction.add(trans)
   }
}

现在假设我想开始记录每次使用此对象添加事务时的历史记录。

public class AccountHistory
{
   private DateTime _historyDate;
   private String _details;

   public AccountHistory(string details)
   {
      _historyDate = DateTime.Now;
      _details = details;
   }
}

在这个级别我通常会做的是将一组历史事件添加到帐户对象中,并添加一行代码以在AddTransaction()方法中创建一个历史事件,如下所示

public AddTransaction(trans as Transaction)
{
   _transaction.add(trans);
   **_historyEvents.add(new AccountHistory("Transaction Added: " + trans.ToString());**
}

现在下一部分是问题开始出现的地方。假设我想进行批量发布,并且我希望保留一份记录,说明在此批量发布中哪些帐户已更改为报表,或者我是否需要稍后撤消。所以我会创建一个像这样的对象。

public class HistoryGroup()
{
   private IList<AccountHistory> _events;
}

从这里我看到一些不同的选项来处理这个问题,因为上面的示例代码无法处理它。

1)在Service类型对象中创建一个函数,该对象循环调用AddTransaction()方法的帐户列表,并创建与HistoryGroup相关的历史记录

 public void AddTransactions(IList<Account> accounts, Transaction trans)
    {
       HistoryGroup history = new HistoryGroup(); 
       for (int x=0;x <=accounts.Count - 1; x++)
       {
         accounts(x).AddTransaction(trans);
         history.AddEvent(new AccountHistory("Added Transaction: " + trans.ToString();
       }
    }

2)将一些类型的HistoryManager对象与要添加的事务一起传递到AddTransaction方法中。然后该函数可以使用历史管理器来创建记录。

好的这篇文章足够长了。如果我不够清楚,请告诉我。谢谢你输入。

3 个答案:

答案 0 :(得分:9)

你的方法可能运行得很好,但让我提出一个替代方案。

为什么不向Account类添加TransactionAdded事件。

然后,您可以从(我猜这里)订阅HistoryGroup对象,以便每次触发Event时都添加一个新的AccountHistory对象。

<强>更新

正如评论中所提到的,实现目标的另一种方法是让HistoryGroup实现一个接口(ITransactionLogger或类似的东西),然后修改Account,以便可以注入ITransactionLogger依赖项。

从复杂性和调试的角度来看,使用这些路径中的任何一个都会使事情变得更容易管理,但是不允许像事件这样的多个记录器。

这将使您的代码更灵活,同时允许对TransactionAdded事件感兴趣的其他消费者进行订阅。

答案 1 :(得分:2)

我在某些方面同意贾斯汀的回答,但OP上的一个标签是POCO;将事件添加到Account类将在某些方面取消POCO。

如果您正在使用AOP等,您可以使用拦截(大多数IoC框架,包括Unity和Castle提供此功能)来获取感兴趣的事务。

拦截的好处是您的Account类与AccountHistory类没有任何耦合,拦截可以根据您想要的任何规则进行高度配置,并且可以在不强制重新编译应用程序的情况下轻松更改(如果您将AccountHistory放入与拦截处理程序不同的程序集)。通过使用拦截,您可以使您的代码更专注于业务领域,而不是可以被视为基础架构任务(审计)。

同样,这是您工具箱的另一种选择;如果您因任何原因不需要通过电线序列化您的POCO,那么通过Justin建议的事件实施观察者模式(GoF)可能是一种更轻量级的方法。

答案 2 :(得分:1)

四人帮似乎都这么认为。交易,历史跟踪和不执行都是command pattern合同的一部分。您可以使用堆栈实现历史记录。以下是包含合同的相关代码片段,请注意并非所有方法都必须实施:

public interface ICommand
{
    void execute();
    void undo();
    void store();
    void load();
}
public class ManagerMacro : ICommand
{
    List<ICommand> Commands;
    Stack commandStack;
    /// <summary>
    /// Use in combination with AddSteps
    /// </summary>
    //public ManagerMacro()
    //{

    //}
    public ManagerMacro(List<ICommand> commands)
    {
        this.Commands = commands;
        this.commandStack = new Stack();
    }

    #region ICommand Members

    public void execute()
    {
        for (int i = 0; i < Commands.Count; i++)
        {
            commandStack.Push(Commands[i]);
            Commands[i].execute();
        }
    }

    public void undo()
    {
        for (int i = 0; i < Commands.Count; i++)
        {
            if (commandStack.Count > 0)
            {
                ICommand Command = (ICommand)commandStack.Pop();
                Command.undo();
            }
        }
    }
    public void store()
    {
        throw new NotImplementedException();
    }

    public void load()
    {
        throw new NotImplementedException();
    }
    #endregion

    public void AddSteps(Steps[] steps)
    {
        foreach (Steps step in steps)
        {
            ICommand thisStep = null;
            switch (step)
            {
                case Steps.Manager1: thisStep = new Step1(); break;
                case Steps.Manager2: thisStep = new Step2(); break;
                case Steps.Manager3: thisStep = new Step3(); break;
                case Steps.Manager4: thisStep = new Step4(); break;
            }
            this.Commands.Add(thisStep);
        }
    }
}

请注意,我也使用工厂模式。