Регистрация, разрешение, использование шаблона деблокирования - PullRequest
0 голосов
/ 12 ноября 2018

Я сейчас читаю книгу Внедрение зависимостей в .NET Марка Симана.В этой книге он рекомендует шаблон Register, Resolve, Release , а также рекомендует, чтобы каждая из этих операций появлялась только один раз в коде вашего приложения.

Моя ситуация следующая:создание приложения, которое обменивается данными с ПЛК (своего рода промышленный встроенный компьютер), используя собственный протокол связи, для которого производитель ПЛК предоставляет библиотеку.В документации библиотеки рекомендуется создавать соединение с ПЛК и поддерживать его открытым;затем с помощью таймера или цикла while следует периодически отправлять запрос на считывание содержимого памяти ПЛК, которое со временем меняется.

Значения, считанные из памяти ПЛК, должны использоваться для работы с базой данных, для которой я намерен использовать Entity Framework.Насколько я понимаю, лучшим вариантом является создание нового dbContext при каждом выполнении цикла, чтобы избежать проблем с кешем или параллелизмом (цикл может потенциально выполняться каждые несколько миллисекунд в течение длительного времени, пока соединение установлено.держать открытым все время).

Моим первым вариантом был вызов Resolve при создании приложения, чтобы создать долгоживущий объект, который был бы внедрен с объектом связи PLC и обрабатывал бы выполнение цикла и поддерживал соединение.Затем в начале выполнения каждого цикла я намеревался снова вызвать Resolve, чтобы создать недолговечный объект, который будет добавлен с новым dbContext и который будет выполнять операции с базой данных.Однако после прочтения совета по этой книге я сомневаюсь, что я на правильном пути.

Моя первая идея состояла в том, чтобы передать делегату долгоживущий объект при его создании, который позволил бы ему создавать новые экземпляры недолговечного объекта (я полагаю, что это фабричный шаблон), таким образом удаляязависимость от контейнера DI от моего долгоживущего объекта.Однако эта конструкция все еще нарушает вышеупомянутую схему.

Как правильно обращаться с инъекцией зависимостей в этой ситуации?

Моя первая попытка без DI:

class NaiveAttempt
{
    private PlcCommunicationObject plcCommunicationObject;
    private Timer repeatedExecutionTimer;

    public NaiveAttempt()
    {
        plcCommunicationObject = new PlcCommunicationObject("192.168.0.10");
        plcCommunicationObject.Connect();

        repeatedExecutionTimer = new Timer(100); //Read values from PLC every 100ms
        repeatedExecutionTimer.Elapsed += (_, __) =>
        {
            var memoryContents = plcCommunicationObject.ReadMemoryContents();
            using (var ctx = new DbContext())
            {
                // Operate upon database
                ctx.SaveChanges();
            }
        }
    }
}

Вторая попытка с использованием DI бедного человека.

class OneLoopObject
{
    private PlcCommunicationObject plcCommunicationObject;
    private Func<DbContext> dbContextFactory;

    public OneLoopObject(PlcCommunicationObject plcCommunicationObject, DbContext dbContext
    {
        this.plcCommunicationObject = plcCommunicationObject;
        this.dbContext = dbContext;
    }

    public void Execute()
    {
        var memoryContents = plcCommunicationObject.ReadMemoryContents();
        // Operate upon database    
    }
}

class LongLivedObject
{
    private PlcCommunicationObject plcCommunicationObject;
    private Timer repeatedExecutionTimer;   
    private Func<OneLoopObject> oneLoopObjectFactory;

    public LongLivedObject(PlcCommunicationObject plcCommunicationObject, Func<PlcCommunicationObject, OneLoopObject> oneLoopObjectFactory)
    {
        this.plcCommunicationObject = plcCommunicationObject;
        this.dbContextFactory = dbContextFactory;
        this repeatedExecutionTimer = new Timer(100);
        this.repeatedExecutionTimer.Elapsed += (_, __) =>
        {
            var loopObject = oneLoopObjectFactory(plcCommunicationObject);
            loopObject.Execute();
        }
    }
}

static class Program
{
    static void Main()
    {
        Func<PlcCommunicationObject, OneLoopObject> oneLoopObjectFactory = plc => new OneLoopObject(plc, new DbContext());
        var myObject = LongLivedObject(new PlcCommunicationObject("192.168.1.1"),  oneLoopObjectFactory)

        Console.ReadLine();
    }
}

Ответы [ 4 ]

0 голосов
/ 13 ноября 2018

В первом издании говорится (глава 3, стр. 82):

В чистом виде шаблон Register Resolve Release гласит, что вы должны делать только одиночный вызов метода на каждом этапе [...], приложение должно содержать только single вызов метода Resolve.

Это описание основано на идее, что ваше приложение содержит только один корневой объект (обычно при написании простого консольного приложения) или одну логическую группу корневых типов, например, Контроллеры MVC. Например, с контроллерами MVC у вас будет пользовательская фабрика контроллеров, которая предоставляется платформой MVC с типом контроллера для сборки. В этом случае эта фабрика будет иметь только один вызов Resolve при предоставлении типа.

Однако существуют случаи, когда в вашем приложении есть несколько групп корневых типов. Например, веб-приложение может иметь сочетание контроллеров API, контроллеров MVC и компонентов просмотра. Для каждой логической группы в вашем приложении, вероятно, будет один вызов Resolve и, следовательно, несколько вызовов Resolve (обычно потому, что каждый корневой тип получает свою собственную фабрику) в вашем приложении.

Существуют и другие веские причины для обратного вызова в контейнер. Например, вы можете отложить построение части графа объектов для борьбы с проблемой зависимостей . Это похоже на ваш случай. Еще одна причина для дополнительного разрешения - использование шаблона Mediator для отправки сообщений определенной реализации (или реализациям), которые могут обработать это сообщение. В этом случае ваша реализация Mediator обычно оборачивает контейнер и вызывает Resolve. Абстракция Посредника, скорее всего, будет определена в библиотеке вашего домена, в то время как реализация Посредника с ее знанием контейнера должна быть определена внутри Корня композиции.

Таким образом, рекомендация о единственном вызове Resolve не должна восприниматься буквально. Реальная цель здесь - построить как можно больше графов объектов за один вызов, по сравнению с тем, чтобы позволить самим классам перезванивать в контейнер для разрешения своих зависимостей (то есть анти-паттерна Service Locator).

Другой важный момент, который ( второе издание из) книги делает

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

Контейнер DI, инкапсулированный в корне композиции, не является локатором службы - это компонент инфраструктуры.

(примечание: эта цитата взята из второго издания ; хотя первое издание также содержит эту информацию, она может быть сформулирована по-другому).

Таким образом, цель паттерна RRR состоит в том, чтобы способствовать инкапсуляции DI-контейнера в корне композиции, поэтому он настаивает на единственном вызове Resolve.

Обратите внимание, что во время написания второго издания мы с Марком хотели переписать обсуждение шаблона RRR. Основной причиной этого было то, что мы обнаружили, что текст сбивает с толку (как показывает ваш вопрос). Однако, в конце концов, у нас закончилось время, поэтому мы решили просто удалить эту сложную дискуссию. Мы чувствовали, что самые важные моменты уже были сделаны.

0 голосов
/ 12 ноября 2018

Похоже, вы хотите иметь возможность как разрешать объекты из контейнера, так и затем освобождать их, и все это без прямой ссылки на контейнер.

Это можно сделать с помощью метода Create и Release в интерфейсе фабрики.

public interface IFooFactory
{
   Foo Create();
   void Release(Foo created);
}

Это позволяет скрыть ссылки на контейнер в реализации IFooFactory.

Вы можете создать собственную реализацию фабрики, но для удобства некоторые контейнеры, такие как Windsor, создадут реализацию фабрики для вас .

var container = new WindsorContainer();
container.AddFacility<TypedFactoryFacility>();
container.Register(Component.For<Foo>());
container.Register(
    Component.For<IFooFactory>()
        .AsFactory()
);

Вы можете внедрить фабрику, вызвать Create, чтобы получить экземпляр того, что фабрика создает, и когда вы закончите с этим, передать этот экземпляр в метод Release.

Виндзор делает это по соглашению. Имена методов не имеют значения. Если вы вызываете метод интерфейса, который возвращает что-то, он пытается разрешить это. Если метод возвращает void и принимает аргумент, он пытается освободить аргумент из контейнера.

За кулисами это примерно так же, как если бы вы написали это:

public class WindsorFooFactory : IFooFactory
{
    private readonly IWindsorContainer _container;

    public WindsorFooFactory(IWindsorContainer container)
    {
        _container = container;
    }

    public Foo Create()
    {
        return _container.Resolve<Foo>();
    }

    public void Release(Foo created)
    {
        _container.Release(created);
    }
}

Заводская реализация"знает" о контейнере, но ничего страшного. Его работа заключается в создании объектов. Фабрика interface не упоминает контейнер, поэтому классы, которые зависят от интерфейса, не связаны с контейнером. Вы можете создать совершенно другую реализацию фабрики, которая не использует контейнер. Если объект не нужно освобождать, у вас может быть метод Release, который ничего не делает.

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

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

0 голосов
/ 12 ноября 2018

Autofac использует Func<> в качестве заводского образца , поэтому вы всегда можете сделать то же самое:

public class Foo()
{
  private readonly Func<Bar> _barFactory;

  public Foo(Func<Bar> barFactory)
  {
    _barFactory = barFactory;
  }
}

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

Тогда вам просто нужно отследить, какие сущности принадлежат извне или принадлежат DI для вашей версии (Утилизировать в C #).

0 голосов
/ 12 ноября 2018

Объединение заводов с DI является распространенным решением. Нет ничего плохого в том, чтобы динамически создавать и утилизировать объекты в вашей программе (гораздо сложнее и сложнее пытаться учесть каждый бит памяти, который вам понадобится заранее).

Я нашел сообщение Марка Симана о регистре, разрешении, шаблоне выпуска (RRR) здесь: http://blog.ploeh.dk/2010/09/29/TheRegisterResolveReleasepattern/

Он утверждает, что ...

Имена происходят от терминологии Виндзорского замка, где мы:

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

Разрешение корневых компонентов

Извлечение компонентов из контейнера

Таким образом, шаблон RRR ограничен контейнером DI. Вы действительно регистрируете и выпускаете компоненты с контейнером один раз в своем приложении. Это ничего не говорит об объектах, не внедряемых через DI, то есть тех объектах, которые создаются динамически при обычном выполнении вашей программы.

Я видел, что в разных статьях используется разная терминология для двух разных типов вещей, которые вы создаете в своей программе относительно DI. Существуют сервисные объекты, то есть те глобальные объекты, которые вводятся через DI в ваше приложение. Затем существуют объекты данных или значений. Они создаются вашей программой динамически по мере необходимости и обычно ограничены какой-то локальной областью действия. Оба совершенно действительны.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...