IoC Factory:接口与代表的优缺点

k3b*_*k3b 27 .net c# design-patterns dependency-injection ioc-container

在任何需要运行时值来构造特定依赖项的地方,Abstract Factory就是解决方案.

我的问题是:为什么很多来源都赞成FactoryInterface而不是FactoryDe​​legate来实现这种模式?两种解决方案的优缺点是什么?

这是一个了解我的意思的例子

如果您的服务需要具有特定上下文的存储库,则服务构造函数需要工厂来创建或访问其存储库.

对此的常见解决方案是创建这样的RepositoryFactoryInterface.

public IRepositoryFactory {
    IRepository Create(ContextInformation context);
}

public class MyService {
    private IRepositoryFactory repositoryFactory;
    public MyService(IRepositoryFactory repositoryFactory)
    {
        this.repositoryFactory = repositoryFactory:
    }

    public void DoSomeService()
    {
        ContextInformation context = ....;

        IRepository repository = this.repositoryFactory.Create(context);

        repository.Load(...);
        ...
        repository.Save(...);
    }
}
Run Code Online (Sandbox Code Playgroud)

您还需要以某种方式实现IRepositoryFactory接口

public MyEf4RepositoryFactory : IRepositoryFactory
{
    IRepository Create(ContextInformation context)
    {
        return new MyEf4Repository(context);
    }
}
Run Code Online (Sandbox Code Playgroud)

...并在应用程序中使用它

public void main()
{
    IRepositoryFactory repoFactory = new MyEf4RepositoryFactory();
    IService service = new MyService(repoFactory); 

    service.DoSomeService();
}
Run Code Online (Sandbox Code Playgroud)

-----主流解决方案结束------

您可以使用像这样需要较少编码的factorydelegate来代替RepositoryFactoryInterface .

public class MyService {
    private Func<ContextInformation, IRepository> repositoryFactory;
    public MyService(Func<ContextInformation, IRepository> repositoryFactory)
    {
        this.repositoryFactory = repositoryFactory:
    }

    public void DoSomeService()
    {
        ContextInformation context = ....;

        IRepository repository = this.repositoryFactory(context);

        repository.Load(...);
        ...
        repository.Save(...);
    }
}
Run Code Online (Sandbox Code Playgroud)

...并在应用程序中使用它

public void main()
{
    IService service = new MyService(context => new MyEf4Repository(context)); 

    service.DoSomeService();
}
Run Code Online (Sandbox Code Playgroud)

在我看来,factorydelegate context => new MyEf4Repository(context)比声明和实现接口更加紧凑IRepositoryFactoryMyEf4RepositoryFactory.

必须有一个原因,我想知道为什么.

这是一个使用接口aproach的示例源:回答is-there-a-pattern-for-initializing-objects-created-via-a-di-container

[更新] 15个月后,在询问这个问题并获得更多Java经验的经验后,我改变了主意:现在我更喜欢接口而不是委托.但我不能说为什么.这只是一种感觉.也许是因为我比较习惯了?

Ste*_*ven 10

在任何需要运行时值来构造特定依赖项的地方,Abstract Factory就是解决方案.

我会反对这一点.不应使用运行时数据构造依赖关系,如此处所述.总之,文章指出:

在构造期间不要将运行时数据注入应用程序组件; 它会导致歧义,使组合根变得更加复杂,并且使得验证DI配置的正确性变得非常困难.相反,让运行时数据流过构造对象图的方法调用.

当我们让运行时数据"流过构造对象图的方法调用"时,您将看到抽象工厂的有用性下降.当运行时数据用于从多个依赖项中进行选择时(与将运行时数据注入依赖项相比),它们可能仍会被使用,但即便如此,抽象工厂通常也不是最佳解决方案,如此处所述.总之,文章指出:

通常,工厂抽象的使用不是考虑其消费者的设计.根据依赖注入原则(DIP),抽象应由其客户端定义,并且由于工厂增加了客户端被迫依赖的依赖关系的数量,因此显然不会创建有利于客户端的抽象,因此我们可以认为这违反了DIP.

相反,Facade,Composite,Mediator和Proxy等模式通常是更好的解决方案.

这并不意味着您不能在应用程序中生成产生依赖关系的代码,但不应将其定义为其他应用程序组件使用的抽象.相反,应将类似工厂的行为封装到定义为组合根的一部分的适配器中.

如果您只将这些类似工厂的逻辑和依赖关系作为组合根的一部分,那么定义IRepositoryFactory或仅使用Func<IRepository>构造此类依赖关系并不重要,因为它IRepositoryFactory也将在组合根中定义(因为该应用程序没有使用此类工厂的业务).

也就是说,在极少数情况下,抽象工厂是正确的抽象(通常在构建可重用框架时会发生),我确实发现工厂接口的使用比使用委托更有意义.它有点冗长,但更清楚的是这种事物的含义是什么.一个IControllerFactory比意图更有意思Func<IController>.

我会说这对于那些不产生依赖关系而非数据值的工厂更为重要.例如,将一个注入Func<DateTime>到构造函数中的示例.这实际意味着什么,它返回什么价值?它是直观的,它返回一个DateTime.Now,或它返回DateTime.Today,还是其他什么?在这种情况下,ITimeProviderGetCurrentTime()方法定义接口会更清楚.

注意:此答案于2017年7月更新,以反映我的最新观点.

  • 另一个令人满意的中间点 - 您可以在许多情况下定义自定义委托类型(主要是为了描述性名称的好处). (2认同)

dvd*_*rle 4

就我个人而言,我一直使用主流解决方案,只是因为我没有想到使用委托。

经过我的思考,我面临着关注点分离的问题。我正在使用 Ninject,并且我不希望我的绑定模块看起来像这样(想象一下存储库本身有一些依赖项):

class IoCModule : NinjectModule
{
    public override Load()
    {
        Bind<Func<Context, IRepository>>()
            .ToConstant( context => new MyEf4Repository(context, Kernel.Get<IRepositoryDependency1>, Kernel.Get<IRepositoryDependency2>) );
    }
}
Run Code Online (Sandbox Code Playgroud)

那根本不可读。因此,我仍然使用完全类型化的抽象工厂来实现关注点分离和可读性。

现在我使用这个问题中描述的 FuncModule (a AutoFac)。所以我可以这样做:

class IoCModule : NinjectModule
{
    public override Load()
    {
        Bind<IRepository>().To<MyEf4Repository>();
        Bind<IRepositoryDependency1>().To<...>();
        Bind<IRepositoryDependency2>().To<...>();
    }
}
Run Code Online (Sandbox Code Playgroud)

让 ninject 帮我找出依赖关系。正如您所看到的,它比使用上述方法更具可读性,并且必须为每个依赖项绑定工厂。这就是我从主流解决方案过渡到委托解决方案的方式。

所以来回答你的问题。我使用主流解决方案的原因是因为我一开始不知道如何用另一种方式来做(这部分是由于大多数博客完全键入抽象工厂造成的,你能看到圆圈吗?)。