C # - Должен ли объект отвечать за создание объекта истории, когда он меняет что-то вроде статуса? - PullRequest
4 голосов
/ 13 июля 2010

Это больше вопрос архитектуры / лучших практик, чем что-либо еще, поэтому, пожалуйста, не стесняйтесь добавлять свои два цента. Я знаю, что я указал статус в заголовке, но это касается любого базового свойства объекта. Я думаю, что приведенный ниже пример аккаунта поможет продемонстрировать мой вопрос немного лучше, чем статус.

Вот пример объекта Account:

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 ]

9 голосов
/ 13 июля 2010

Ваш метод может работать просто отлично, но позвольте мне предложить альтернативу.

Почему бы не добавить событие TransactionAdded в класс Account.

Затем вы можете подписаться на событие от (Iя догадываюсь здесь) объект HistoryGroup, так что новый объект AccountHistory добавлялся каждый раз при возникновении события.

UPDATE

Как уже упоминалось в комментариях, еще один методДостижение цели будет состоять в том, чтобы HistoryGroup реализовал интерфейс (ITransactionLogger или что-то подобное), а затем изменил Account, чтобы можно было ввести зависимость ITransactionLogger.

Использование любого из этих маршрутов делает управление несколькими вещами немного прощесложность и отладка, но не допускает использование нескольких регистраторов, таких как события.

Это сделает ваш код немного более гибким и в то же время позволит другим пользователям, заинтересованным в событии TransactionAdded, подписаться.

2 голосов
/ 13 июля 2010

В некотором смысле я согласен с ответом Джастина, но одним из тегов на ОП является POCO;добавление события в класс Account в некотором смысле приведет к отмене POCO вашего POCO.

Если вы в AOP и других подобных областях, вы можете использовать перехват (большинство платформ IoC, включая Unity и Castle предлагают эту функциональность) для захвата транзакций, представляющих интерес.

Преимущество перехвата заключается в том, что ваш класс Account не имеет никакой связи с классом AccountHistory, перехват легко настраивается в соответствии с любыми правилами, которые вы хотите, и его легко изменить без принудительной перекомпиляции приложения (если выAccountHistory в другую сборку с обработчиками перехвата).Используя перехват, вы делаете свой код более ориентированным на бизнес-область, а не на то, что можно считать задачей инфраструктуры (аудит).

Опять же, это еще одна альтернатива для вашего набора инструментов;если по какой-либо причине вам не нужно сериализовать свои POCO по проводам, то реализация шаблона наблюдателя (GoF) с помощью событий, предложенных Джастином, может быть более легким подходом.

1 голос
/ 13 июля 2010

Кажется, так думает банда из четырех человек. Транзакции, отслеживание истории и отсутствие операций - все это часть шаблона команды контракта. Вы можете реализовать историю со стеком. Вот фрагмент соответствующего кода, включая контракт, обратите внимание, что не все методы есть или должны быть реализованы:

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

Обратите внимание, что я также использую фабричный шаблон.

...