Регистрация цепочки зависимостей Autofac

Вопрос

Как создать AutoFac ContainerBuilder таким образом, чтобы мои подчиненные зависимости были правильно разрешены (при условии, что имеется более одной конкретной реализации интерфейса)? Регистрация / разрешение по умолчанию не будет работать, потому что у меня есть несколько конкретных реализаций под-зависимости, а разрешение под-зависимости зависит от разрешения первичного объекта. Во всех случаях я хочу использовать конструктор инъекций.

сценарий

Например, допустим, мне нужно распечатать квитанцию, поэтому я создаю интерфейс IReceipt с одним методом PrintReceipt().

Но мне нужно распечатать 3 вида квитанций

  1. Стандартная квитанция
  2. Подарочная квитанция
  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 уровня зависимостей.

  1. Главный
  2. Квитанция об объекте
  3. Процессоры и форматеры
  4. Принтер (используется только на процессоре принтера)(возможно, он выражает зависимость от сервера электронной почты, но я думаю, что этот пример довольно понятен как есть).

Используя Autofac.Core.ResolvedParameter, мы можем ссылаться на другие зарегистрированные объекты (по имени). Это будет держать регистрацию долго, но ровно. Любая иерархия выражается (поверхностно - если это слово) только как родитель-потомок и очень многократно используется. Resolve вызывается только для корневых композиционных объектов - 3 механизма получения (Standard, Gift и Email). Для каждого корневого составного объекта теперь можно разрешить всю цепочку зависимостей.

Другие вопросы по тегам