@vilix

Как избежать IoC service locator anti-pattern в данном примере?

У меня есть тестовый рабочий код на С# + WCF+ ninject (wcf тут участвует только потому, что планируемый проект на wcf).
Вкратце: у меня есть сущность SimpleController в БД, я хочу по параметру SimpleController, получать нужную реализацию интерфейса IControllerProccesor.
Вот как это сейчас реализовано:
//program.cs
...
private static StandardKernel CreateKernel()
{
    var kernel = new StandardKernel();

    kernel.Bind<IDbConnectionFactory>().ToMethod(c =>
        new OrmLiteConnectionFactory(
            conString, 
            SqlServerDialect.Provider))
        .InSingletonScope();

    kernel.Bind<IControllerProccessor>().To<ControllerProccessor>()
        .WhenInjectedInto<HelloWorldService>().WithConstructorArgument(kernel);

    kernel.Bind<IControllerProccessor>().To<Vendor1Proccessor>()
        .Named("vendor1"); 

    kernel.Bind<IControllerProccessor>().To<Vendor2Proccessor>()
        .Named("vendor2"); 

    return kernel;
}
...

//IControllerProccessor.cs
public interface IControllerProccessor
{
    SimpleController Ctr { get; set; }
    bool sendMsg(string msg);
}

//Vendor1Proccessor.cs
public class Vendor1Proccessor : IControllerProccessor
{
    public SimpleController Ctr {get; set;}

    public bool sendMsg(string msg)
    {
        //specific to vendor code, for example calls to vendor1 SDK
        Console.WriteLine("Controller id: {0} vendor:{1} recivied msg: {2}",
            Ctr.Id,
            "Vendor1Class",
            msg);
        return true;
    }
}

//Vendor2Proccessor.cs
public class Vendor2Proccessor : IControllerProccessor
{
    public SimpleController Ctr { get; set; }

    public bool sendMsg(string msg)
    {
        //specific to vendor code, for example calls to vendor1 SDK
        Console.WriteLine("Controller id: {0} vendor:{1} recivied msg: {2}",
            Ctr.Id,
            "Vendor2Class",
            msg);
        return true;
    }
}

//ControllerProccessor.cs
public class ControllerProccessor : IControllerProccessor
{
    public SimpleController Ctr {get; set;}

    private readonly IKernel kernel;

    public ControllerProccessor(IKernel _kernel)
    {
        kernel = _kernel;
    }

    public bool sendMsg(string msg)
    {
        var param = new Ninject.Parameters.PropertyValue("Ctr", Ctr);
        return kernel.Get<IControllerProccessor>(Ctr.Vendor, param).sendMsg(msg);
    }
}

//HelloWorldService.cs
public class HelloWorldService : IHelloWorldService
{
    private readonly IDbConnectionFactory dbFactory;
    private readonly IControllerProccessor ctrProccessor;

    public HelloWorldService(IDbConnectionFactory _dbFactory, IControllerProccessor _ctrProccesor)
    {
        dbFactory = _dbFactory;
        ctrProccessor = _ctrProccesor;
    }

    public bool sendMsgToAllControllers(string msg)
    {
        var db = dbFactory.Open();
        var controllers = db.Select<SimpleController>();

        foreach(var ctr in controllers)
        {
            ctrProccessor.Ctr = ctr;
            ctrProccessor.sendMsg(msg);
        }
        db.Close();

        return true;
    }

}

//SimpleController.cs
[DataContract]
[Alias("SimpleController")]
public class SimpleController
{
    [AutoIncrement]
    [DataMember]
    public int? Id { get; set; }
    [DataMember]
    public string Vendor { get; set; }
}


Главный вопрос является ли строчки ниже анти паттерном и как это все дело отрефакторить в стиле DI? Или это не является анти паттерном в моем случае? Ведь ControllerProccesor объявляется в composition root и kernel используется только в нем, т.е kernel по сути же не выходит за composition root.
var param = new Ninject.Parameters.PropertyValue("Ctr", Ctr);
        return kernel.Get<IControllerProccessor>(Ctr.Vendor, param).sendMsg(msg);


Второй вопрос: может это вообщем можно как то отрефакторить лучше? например так чтобы не писать в каждом методе строчки выше. Тут можно конечно создавать на каждый ctr new ControllerProccesor(ctr, kernel) - но тогда контейнер выходит за composition root - хотя так может и удобнее будет, но не зря же пишут что это анти паттерн, значит в будущем это может вылезти.

В будущем все биндинги будут в рантайме, а реализации IControllerProccesor в подключаемых в рантайме .dll (кроме ControllerProccesor - который по сути является как wrapper для всех остальных процессоров) - т.е в итоге получу систему плагинов.
  • Вопрос задан
  • 344 просмотра
Решения вопроса 2
@dmitryKovalskiy
программист средней руки
Сделайте фабрику получения объектов IControllerProccessor.
Ответ написан
@vilix Автор вопроса
После ответа Дмитрий Ковальский сделал след. factory:
public class ControllerProccesorFactory : IControllerProccesorFactory
{
    private readonly IKernel kernel;

    public ControllerProccesorFactory(IKernel _kernel)
    {
        kernel = _kernel;
    }

    public IControllerProccessor Create(SimpleController ctr)
    {
        IControllerProccessor proccessor = kernel.Get<IControllerProccessor>(ctr.Vendor);
        proccessor.Ctr = ctr;
        return proccessor;
    }
}

В Bindigs добавил:
kernel.Bind<IControllerProccesorFactory>().To<ControllerProccesorFactory>()
                .WhenInjectedInto<HelloWorldService>().WithConstructorArgument(kernel);

ControllerProccesor - удалил, использую теперь в сервисе так:
public class HelloWorldService : IHelloWorldService
{
    private readonly IDbConnectionFactory dbFactory;
    private readonly IControllerProccesorFactory ctrProcFactory;

    public HelloWorldService(IDbConnectionFactory _dbFactory, IControllerProccesorFactory _ctrProcFactory)
    {
        dbFactory = _dbFactory;
        ctrProcFactory = _ctrProcFactory;
    }

    public bool sendMsgToAllControllers(string msg)
    {
        var db = dbFactory.Open();
        var controllers = db.Select<SimpleController>();

        foreach(var ctr in controllers)
        {
            var ctrProc = ctrProcFactory.Create(ctr);
            ctrProc.sendMsg(msg);
        }
        db.Close();

        return true;
    }

}


Пока это решение полностью устраивает, и минусов в нем не вижу, но любым замечаниям и предложениям буду рад в комментариях или в ответах.
Ответ написан
Комментировать
Пригласить эксперта
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы