Регистрация цепочки зависимостей Autofac
Вопрос
Как создать AutoFac ContainerBuilder таким образом, чтобы мои подчиненные зависимости были правильно разрешены (при условии, что имеется более одной конкретной реализации интерфейса)? Регистрация / разрешение по умолчанию не будет работать, потому что у меня есть несколько конкретных реализаций под-зависимости, а разрешение под-зависимости зависит от разрешения первичного объекта. Во всех случаях я хочу использовать конструктор инъекций.
сценарий
Например, допустим, мне нужно распечатать квитанцию, поэтому я создаю интерфейс IReceipt с одним методом PrintReceipt().
Но мне нужно распечатать 3 вида квитанций
- Стандартная квитанция
- Подарочная квитанция
- Квитанция по электронной почте
Все типы квитанций имеют разные форматы, и квитанция по электронной почте вообще не печатается, а отправляется по электронной почте. Поэтому я бы хотел, чтобы мой IReceipt имел возможность зависеть от форматера и процессора. Скажем, я изобрел IProcessor с методом Process(), и это может быть либо процессор принтера, либо процессор электронной почты (процессор принтера отвечает за связь с принтером, а процессор электронной почты отвечает за связь с SMTP-сервером)., Кроме того, я создаю IFormatter, и он может передавать входные данные объекту процессора, отформатированному как стандартная квитанция, или подарочная квитанция, или даже HTML для квитанции по электронной почте. Таким образом, конструктор в любой конкретной реализации IReciept теперь требует двух зависимостей - IFormatter и IProcessor.
Теперь в корне я должен решить, разрешаю ли я IReceipt, предназначенный для стандартной квитанции, квитанции о подарке или квитанции по электронной почте. Я хотел бы вызвать метод Resolve() контейнера, передавая необходимые параметры, чтобы он разрешил правильный IReceipt. Кроме того, я хотел бы, чтобы служба ContainerBuilder для регистрации знала, что, если я попытаюсь разрешить конкретную реализацию стандартной квитанции IReceipt, она должна разрешить подчиненные зависимости с помощью правильного IFormatter стандартной квитанции и правильного IP-обработчика стандартной квитанции. То же самое относится и к сценарию получения подарка и сценарию получения по электронной почте.
резюмировать
Итак, во всем этом - мой вопрос - как мне создать ContainerBuilder, чтобы под-зависимости определялись во время разработки, и чтобы один вызов Resolve() правильно идентифицировал требуемые конкретные реализации? Я не ищу решения, чтобы поговорить с принтером или опубликовать HTML. Этот вопрос относится исключительно к методологии регистрации и разрешения Autofac. На другом клиенте я использовал эту точную тактику, используя CastleWindsor, но мой текущий клиент использует Autofac.
1 ответ
Хорошо, поэтому я нашел способ выполнения цепочки зависимостей. Опять же, необходимо вызвать один раз для каждого корневого композиционного объекта и разрешить все подчиненные зависимости, удовлетворяющие всей цепочке вниз. Не пытаясь вступить в религиозную дискуссию о передовой практике - следует ли внедрять фабричные методы, службы поиска и т. Д. Я намеренно пропустил область видимости IoC, поскольку это не предмет моего первоначального вопроса.
Этот надуманный пример не реализует функции электронной почты или принтера, но сейчас он заглушен. Весь смысл в том, чтобы показать, как заранее определить всю цепочку зависимостей. Теперь автоматизированные модульные тесты станут проще в реализации.
Код
Основная программа
using System;
using System.Collections.Generic;
using Autofac;
namespace MyAutoFacTest
{
class Program
{
static void Main(string[] args)
{
// CREATE THE IOC ENGINE AND CONTAINER
var builder = new Autofac.ContainerBuilder();
Autofac.IContainer container;
// CREATE THE DEPENDENCY CHAIN REGISTRATION
builder.RegisterType<Printer>()
.Named<IPrinter>("My Default Printer");
builder.RegisterType<PrinterProcessor>()
.Named<IProcessor>("PrinterProcessor")
.WithParameter(Autofac.Core.ResolvedParameter.ForNamed<IPrinter>("My Default Printer"));
builder.RegisterType<EmailProcessor>()
.Named<IProcessor>("EmailProcessor");
builder.RegisterType<StandardReceiptFormatter>()
.Named<IFormatter>("StandardReceiptFormatter");
builder.RegisterType<GiftReceiptFormatter>()
.Named<IFormatter>("GiftReceiptFormatter");
builder.RegisterType<EmailReceiptFormatter>()
.Named<IFormatter>("EmailReceiptFormatter");
builder.RegisterType<Receipt>()
.Named<IReceipt>("StandardReceipt")
.WithParameter(Autofac.Core.ResolvedParameter.ForNamed<IProcessor>("PrinterProcessor"))
.WithParameter(Autofac.Core.ResolvedParameter.ForNamed<IFormatter>("StandardReceiptFormatter"));
builder.RegisterType<Receipt>()
.Named<IReceipt>("GiftReceipt")
.WithParameter(Autofac.Core.ResolvedParameter.ForNamed<IProcessor>("PrinterProcessor"))
.WithParameter(Autofac.Core.ResolvedParameter.ForNamed<IFormatter>("GiftReceiptFormatter"));
builder.RegisterType<Receipt>()
.Named<IReceipt>("EmailReceipt")
.WithParameter(Autofac.Core.ResolvedParameter.ForNamed<IProcessor>("EmailProcessor"))
.WithParameter(Autofac.Core.ResolvedParameter.ForNamed<IFormatter>("EmailReceiptFormatter"));
// COMPILE THE AUTOFAC REGISTRATION
container = builder.Build();
// SETUP INITIALIZATION STUFF - THINGS THAT WOULD ORDINARILY HAPPEN AS PART OF EXTERNAL SYSTEMS INTEGRATION
int someBogusDatabaseIdentifier = 1;
var standardReceipt = container.ResolveNamed<IReceipt>("StandardReceipt");
standardReceipt.PrintReceipt(someBogusDatabaseIdentifier);
var giftReceipt = container.ResolveNamed<IReceipt>("GiftReceipt");
giftReceipt.PrintReceipt(someBogusDatabaseIdentifier);
var emailReceipt = container.ResolveNamed<IReceipt>("EmailReceipt");
emailReceipt.PrintReceipt(someBogusDatabaseIdentifier);
Console.ReadLine();
}
}
}
Интерфейсы
IPrinter
namespace MyAutoFacTest
{
public interface IPrinter
{
void Print();
}
}
IReceipt
namespace MyAutoFacTest
{
public interface IReceipt
{
void PrintReceipt(int id);
}
}
IProcessor
namespace MyAutoFacTest
{
public interface IProcessor
{
void Process(string formattedString);
}
}
IFormatter
namespace MyAutoFacTest
{
public interface IFormatter
{
string GetFormattedString(int id);
}
}
Конкретные реализации
Сначала покажет листовые зависимости...
принтер
using System;
namespace MyAutoFacTest
{
public class Printer : IPrinter
{
public void Print()
{
Console.WriteLine("Printer is printing");
}
}
}
Процессор принтера
using System;
namespace MyAutoFacTest
{
public class PrinterProcessor : IProcessor
{
private IPrinter _printer;
public PrinterProcessor(IPrinter printer)
{
this._printer = printer;
}
public void Process(string formattedString)
{
Console.WriteLine("Printer processor sending receipt to printer.");
this._printer.Print();
}
}
}
Процессор электронной почты
using System;
namespace MyAutoFacTest
{
public class EmailProcessor : IProcessor
{
public void Process(string formattedString)
{
Console.WriteLine("Email Processor sending out an email receipt");
}
}
}
Стандартный форматер квитанции
namespace MyAutoFacTest
{
public class StandardReceiptFormatter : IFormatter
{
public string GetFormattedString(int id)
{
return "StandardReceiptFormatter formatted string";
}
}
}
Форматирование подарочной квитанции
namespace MyAutoFacTest
{
public class GiftReceiptFormatter : IFormatter
{
public string GetFormattedString(int id)
{
return "GiftReceiptFormatter formatted string";
}
}
}
Форматер электронной почты квитанции
namespace MyAutoFacTest
{
public class EmailReceiptFormatter : IFormatter
{
public string GetFormattedString(int id)
{
return "EmailReceiptFormatter formatted string";
}
}
}
Чек
using System;
namespace MyAutoFacTest
{
public class Receipt : IReceipt
{
private IFormatter _formatter;
private IProcessor _processor;
public Receipt(IFormatter formatter, IProcessor processor)
{
this._formatter = formatter;
this._processor = processor;
}
public Receipt(IFormatter formatter)
{
this._formatter = formatter;
}
public Receipt(IProcessor processor)
{
this._processor = processor;
}
public void PrintReceipt(int id)
{
var formattedString = this._formatter.GetFormattedString(id);
Console.WriteLine(formattedString);
this._processor.Process(formattedString);
}
}
}
Заключение
Большая часть шума, связанного с объектами, которые нужно создать, связана в одном месте. На практике я, скорее всего, перенесу регистрацию в отдельный фрагмент кода (возможно, статический класс). Исключение шума из функциональных классов дает хороший чистый код с акцентом на функциональные намерения. Вся перестройка находится на ранней стадии процесса, а теперь уже не в пути.
Что касается регистрации, в частности, мой надуманный пример имеет 4 уровня зависимостей.
- Главный
- Квитанция об объекте
- Процессоры и форматеры
- Принтер (используется только на процессоре принтера)(возможно, он выражает зависимость от сервера электронной почты, но я думаю, что этот пример довольно понятен как есть).
Используя Autofac.Core.ResolvedParameter, мы можем ссылаться на другие зарегистрированные объекты (по имени). Это будет держать регистрацию долго, но ровно. Любая иерархия выражается (поверхностно - если это слово) только как родитель-потомок и очень многократно используется. Resolve вызывается только для корневых композиционных объектов - 3 механизма получения (Standard, Gift и Email). Для каждого корневого составного объекта теперь можно разрешить всю цепочку зависимостей.