2015-12-21 2 views
3

Существует объект «Аудит», который используется во всей базе кода, которую я пытаюсь реорганизовать, чтобы разрешить инъекцию зависимостей и, в конечном итоге, улучшить модульное тестирование. До этого момента у меня не было проблем с созданием интерфейсов для моих классов и их инжекции через конструктор. Однако этот класс отличается. Я понимаю, почему/как все по-другому, но я не уверен, как это сделать, чтобы работать «правильно».Как выполнить преобразование репозитория объектов с использованием инъекции зависимостей?

Вот пример (упрощенная вниз версия, но проблема сохраняется даже в приведенном выше примере):

namespace ConsoleApplication1.test.DI.Original 
{ 
    public class MultiUseDependencies 
    { 
     public MultiUseDependencies() 
     { 

     } 

     public void Update() 
     { 
      Audit a = new Audit(); 
      a.preAuditValues = "Update"; 

      // if data already exists, delete it 
      this.Delete(); 

      // Update values, implementation not important 

      // Audit changes to the data 
      a.AuditInformation(); 
     } 

     public void Delete() 
     { 
      Audit a = new Audit(); 
      a.preAuditValues = "Delete"; 

      // Delete data, implementation omitted. 

      a.AuditInformation(); 
     } 
    } 

    public class Audit 
    { 
     public string preAuditValues { get; set; } 

     public void AuditInformation() 
     { 
      Console.WriteLine("Audited {0}", preAuditValues); 
     } 
    } 
} 

В выше, функции Update (реализация не показана) получает версию «до изменения» в данные, удаляет данные (и проверяет их), вставляет/обновляет изменения данных, затем проверяет вставку/обновление.

Если бы я был бежать из консольного приложения:

Console.WriteLine("\n"); 
test.DI.Original.MultiUseDependencies mud = new test.DI.Original.MultiUseDependencies(); 
mud.Update(); 

Я хотел бы получить:

аудированной Удалить

аудированные Update

Это ожидаемое поведение , Теперь, когда класс реализован, я уже вижу, что проблема будет, но я не уверен, как ее исправить. См (начальная) реорганизовать с DI:

namespace ConsoleApplication1.test.DI.Refactored 
{ 
    public class MultiUseDependencies 
    { 

     private readonly IAudit _audit; 

     public MultiUseDependencies(IAudit audit) 
     { 
      _audit = audit; 
     } 

     public void Update() 
     { 
      _audit.preAuditValues = "Update"; 

      // if data already exists, delete it 
      this.Delete(); 

      // Update values, implementation not important 

      // Audit changes to the data 
      _audit.AuditInformation(); 
     } 

     public void Delete() 
     { 
      _audit.preAuditValues = "Delete"; 

      // Delete data, implementation omitted. 

      _audit.AuditInformation(); 
     } 
    } 

    public interface IAudit 
    { 
     string preAuditValues { get; set; } 
     void AuditInformation(); 
    } 

    public class Audit : IAudit 
    { 
     public string preAuditValues { get; set; } 

     public void AuditInformation() 
     { 
      Console.WriteLine("Audited {0}", preAuditValues); 
     } 
    } 
} 

Продолжительность:

Console.WriteLine("\n"); 
test.DI.Refactored.MultiUseDependencies mudRefactored = new test.DI.Refactored.MultiUseDependencies(new test.DI.Refactored.Audit()); 
mudRefactored.Update(); 

я (как и ожидалось, но неправильно):

аудированной Удалить

аудированной Удалить

Вышеизложенное ожидается на основе реализации, но неверно в соответствии с первоначальным поведением. Я не знаю, как это сделать. Первоначальная реализация опирается на различные Audit s, чтобы правильно отслеживать, что меняется. Когда я перехожу к реализации IAudit в рефакторе, я получаю только один экземпляр Audit, где эти два являются головками друг друга.

В основном до рефакторинга Audit находится на уровне функции. После рефакторинга Audit занят классом.

Есть ли простой способ исправить это?

Вот скрипку с ним в действии: https://dotnetfiddle.net/YbpTm4

+1

Аудит, наряду с протоколированием, безопасностью и кэшированием, представляет собой ** Cross Cutting Concern **, и одно из обычных подозреваемых в аспекте ориентированного на программирование (АОП). Используйте Декораторы или Перехват вместо того, чтобы вводить Услуги для таких проблем. См. http://stackoverflow.com/a/7906547/126014 или http://programmers.stackexchange.com/a/139113/19115 –

+0

@MarkSeemann, в то время как украшение или перехват хорошо работают для чего-то вроде входа в журнал и выхода, какой подход например, поддерживайте подробное ведение журнала, например, из-за метода? –

+0

@DavidOsborne Зачем вам нужно подробное ведение журнала в рамках метода? –

ответ

4

Проблема заключается в дизайне. Audit - это объект, который является mutatable и делает его данными во время выполнения. Инъекция runtime data into the constructors of your components is an anti-pattern.

Решение изменить конструкцию, например, путем определения IAudit абстракции, как это:

public interface IAuditHandler { 
    void AuditInformation(string preAuditValues); 
} 

Для этой абстракции можно создать следующую реализацию:

public class AuditHandler : IAuditHandler { 
    public void AuditInformation(string preAuditValues) { 
     var audit = new Audit(); 
     audit.preAuditValues = preAuditValues; 
     audit.AuditInformation(); 
    } 
} 

потребители могут прямо сейчас зависит от IAuditHandler:

public class MultiUseDependencies 
{ 
    private readonly IAuditHandler _auditHandler; 

    public MultiUseDependencies(IAuditHandler auditHandler) { 
     _auditHandler = auditHandler; 
    } 

    public void Update() { 
     this.Delete(); 

     _auditHandler.AuditInformation("Update"); 
    } 

    public void Delete() { 
     // Delete data, implementation omitted. 

     _auditHandler.AuditInformation("Delete"); 
    } 
} 

Но я должен сделать еще один шаг вперед, потому что с вашим нынешним подходом вы загрязняете бизнес-код сквозными проблемами. Код для контрольного журнала распространяется и дублируется на всей базе кода.

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

+0

Я согласен с @Steven: проблема связана с состоянием компонента Audit, а не с дизайном DI. ИМХО, внедрение компонента аудита представляется разумным. Существует аргумент, что контекст Ambient может быть лучше, но он обсуждается. –

+1

@DavidOsborne: Я бы не согласился с тем, что шаблон Ambient Context полезен здесь. Это все равно приведет к тому, что бизнес-компоненты будут иметь вторую ответственность за регистрацию этой информации аудита. Это проблема, которая должна быть отделена от такого компонента. Аудит трейлинг может быть применен к системе с использованием украшения. – Steven

+0

Несомненно, это будет зависеть от степени детализации регистрации? Если вы хотите только зарегистрировать запись и выйти, то украшение идеально. Тем не менее, я не уверен, как украшение даст вам возможность переплетать подробные записи в бизнес-код? –

0

Попробуйте это:

public void Update() 
{ 
    // if data already exists, delete it 
    this.Delete(); 

    //preAuditValues should be changed after the delete or it will keep 
    //the old value 

    _audit.preAuditValues = "Update"; 

    // Update values, implementation not important 

    // Audit changes to the data 
    _audit.AuditInformation(); 
} 

Или это должно работать также:

public void Delete() 
{ 
    string oldValue = _audit.preAuditValues; 
    _audit.preAuditValues = "Delete"; 

    // Delete data, implementation omitted. 

    _audit.AuditInformation(); 
    //Restoring oldValue after finished with Delete 
    _audit.preAuditValues = oldValue ; 
}