Я могу только описать, что мы придумали. Мы заимствовали синтаксис юзабилити и тому подобное у различных онлайн-библиотек, но весь наш код.
По сути, у нас есть то, что мы называем ServiceContainer, объектом. Его всегда есть глобальный экземпляр, одиночная копия, если хотите, статическая, и, следовательно, в веб-приложении, совместно используемая всеми пользователями в домене приложения.
Контейнер ServiceContainer содержит правила. В правилах говорится что-то вроде Если кто-то запрашивает объект типа XYZ, вот как вы его предоставите .
Например, правило может состоять в том, что для того, чтобы некоторый код получил объект, который реализует IDbConnection, контейнер создавал, настраивал и возвращал новый объект SqlConnection.
Таким образом, рассматриваемый код не знает и не заботится о том, что он использует объект SqlConnection, а не объект OleDbConnection.
Написав это, я понимаю, что это не очень хороший пример, потому что в конечном итоге вы запрашиваете соединение для командных объектов, и синтаксис SQL, который вы задаете этому объекту, должен соответствовать типу соединения. Но если мы можем игнорировать этот момент прямо сейчас, код не будет знать, что он подключается к SQL Server, он просто знает, что у него есть объект подключения.
Теперь, код , о котором идет речь, здесь нужно будет указать контейнер, который он должен использовать, и, следовательно, правила. Это означает, что с точки зрения модульного тестирования я мог бы создать новый экземпляр ServiceContainer, записать в него новые правила для целей тестирования и попросить код выполнить свою работу. В конечном счете, коду потребовалось бы выполнить некоторый SQl (в данном случае), и вместо того, чтобы общаться с реальной базой данных, он вызвал бы мою тестовую реализацию IDbConnection и IDbCommand и таким образом дал бы мне возможность проверить, что все работает.
Что еще более важно, это дает мне возможность вернуть обратно фиктивные данные, соответствующие тесту, без необходимости макетирования всей базы данных.
Теперь, для части инъекция , в нашем случае мы можем попросить контейнер предоставить нам объекты, которые должны быть сконструированы, которые опираются на другие объекты.
Например, допустим, у нас есть интерфейс IDataAccessLayer и реализация MSSQLDataAccessLayer.
Хотя интерфейс не дает нам внешнего признака того, что он ведет какую-либо запись, фактическая реализация здесь должна иметь место для регистрации всего SQL, который он выполняет. Таким образом, конструктор для класса может выглядеть так:
public MSSQLDataAccessLayer(ILogger logger) { ... }
В объекте ServiceContainer мы зарегистрировали следующие правила (это наш синтаксис, вы нигде больше этого не найдете, но ему должно быть достаточно легко следовать):
ServiceContainer.Global.RegisterFactory<ILogger, FileLogger>()
.FactoryScoped()
.WithParameters(
new Parameter("directory", @"C:\Temp")
);
ServiceContainer.Global.RegisterFactory<IDataAccessLayer, MSSQLDataAccessLayer>()
.FactoryScoped();
FactoryScoped означает, что каждый раз, когда я запрашиваю контейнер для объекта, я получаю новый.
Правила, если я напишу их по-английски, будут такими:
- Если кому-то нужна реализация ILogger, создайте новый объект FileLogger, возьмите конструктор, которому нужен параметр "directory", и используйте этот конструктор, передавая "C: \ Temp" в качестве аргумента
- Если кому-то нужна реализация IDataAccessLayer, создайте новый MSSQLDataAccessLayer
Обратите внимание, что я говорил ранее, что конструктор MSSQLDataAccessLayer принимает ILogger, но я не указал здесь никаких параметров? Это дает мне следующий код для получения доступа к объекту уровня доступа:
IDataAccessLayer dal = ServiceContainer.Global.Resolve<IDataAccessLayer>();
Что происходит сейчас, так это то, что объект контейнера выясняет, что это объект MSSQLDataAccessLayer и что у него есть конструктор. Этому конструктору требуется объект ILogger, но вот, контейнер знает, как его создать. Контейнер, таким образом, создаст новый объект FileLogger и передаст его конструктору объекта MSSQLDataAccessLayer без вывода сообщений.
Конфигурирование большей части зависимостей приложения, таким образом, может быть выполнено один раз, где-то в центре и выполнено во время запуска, в то время как остальная часть кода блаженно не знает обо всей магии, происходящей здесь.
В целях модульного тестирования я могу переписать правила, чтобы предоставить свой собственный объект-регистратор, который просто хранит записанный текст в памяти, что позволяет мне легко проверить, действительно ли регистрировалось то, что, как я ожидал, регистрируемый код, без необходимости прочитайте файл позже.
Правила дают нам много возможностей относительно того, как на самом деле предоставлять экземпляры объектов:
- Из делегата / метода, что означает, что мы можем сделать всю магию создания зависимых объектов самостоятельно, если мы хотим
- Автоматически из конструктора (либо автоматически выясняется, какой из них использовать, либо мы можем предоставить достаточно фиктивных параметров по именам / типам, чтобы выбрать один)
- Или мы можем предоставить существующий экземпляр контейнеру (тогда он будет похож на одиночный)
Мы посмотрели на autofac , прежде чем придумать свой собственный, в основном мы просто посмотрели на вики, показывающие примеры синтаксиса вызовов, а затем сели и написали нашу собственную систему, которая делала то, что нам нужно.