Nov 6, 2009

Inversion of control. Управление зависимостями.

Данный пост является некоторым следствием работы над последним проектом. В этом проекте использовалась S#arp архитектура, которая обеспечивает отличную возможность для юнит тестирования и каркас для создания корпоративных веб приложений. Так же крайне полезно будет прочесть статью касающуюся зависимостей. Я считаю что разобраться в чем то лучше всего позволяет попытка объяснить это другому. Именно это и я и хочу сделать.

Рассмотрим пример программы. Пусть приложению необходима некоторая система ведения логов. Логи сохраняются в файл для дальнейшего анализа. Для подобных требований вполне логично было бы использовать следующий код:

public class Logger 
{ 
 public void LogMessage(string message) 
 { 
  using(FileStream fileStream = new FileStream("log.txt", FileMode.Append, FileAccess.Write)) 
  { 
   StreamWriter writer = new StreamWriter(fileStream); writer.Write(message); 
    }
 } 
} 

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

Получив такое требование конечно хотелось бы добавить новый метод LogMessage(string message, bool storeInDataBase), который принимает дополнительный параметр типа bool и если он true пишет все что надо в базу.

Проблема в этом подходе я думаю уже видна – если в дальнейшем мы захотим писать не только базу данных и в файл, но в Windowns EventLog, то что, добавлять еще один флаг?

На данный момент диаграмма зависимостей будет выглядеть следующим образом:

image

Наш класс Logger напрямую зависит от файловой системы. Чтобы избавится от этой зависимости можно использовать принцип Inversion of control. Для этого выделим интерфейс, который будет отвечать за сохранение информации в лог:

public interface ILogContainer 
{ 
 void SaveMessage(string message); 
} 

И каждый класс, который который будет отвечать за сохранение логов пусть реализует данный интерфейс. Например для тех же файлов это может быть такой класс:

public class FileLogContainer: ILogContainer 
{ 
 public void SaveMessage(string message) 
 { 
  using (FileStream fileStream = new FileStream("log.txt", FileMode.Append, FileAccess.Write)) 
  { 
   StreamWriter writer = new StreamWriter(fileStream); writer.Write(message); 
  } 
 } 
} 

И тогда использование будет выглядеть следующим образом:

public class Logger 
{ 
 private ILogContainer logContainer; 
 public Logger(ILogContainer logContainer) 
 { 
  this.logContainer = logContainer; 
 } 
 public void LogMessage(string message) 
 { 
  logContainer. SaveMessage(message); 
 } 
}

После этого преобразования диаграмма зависимостей будет выглядеть:

image

Из диаграммы понятно почему этот подход называется Inversion of control, получается что зависимость от файловой системы была как бы развернута в обратную сторону.

Что нам дает этот подход? Помимо того, что теперь мы можем использовать любое хранилище логов, реализующее ILogContainer, полученный результат отлично поддается тестированию. Интерфейс ILogContainer дает точку в которй можно встроить mock object, с помощью которого можно правильно проверить работу класса Logger.

1 comment:

  1. А когда файл закрывать?

    ReplyDelete