IoC Factory:接口与委托的优缺点

发布于 2024-10-25 12:14:45 字数 2817 浏览 2 评论 0原文

任何需要运行时值来构造特定依赖项的地方,抽象工厂都是解决方案。

我的问题是:为什么许多来源倾向于使用 FactoryInterface 而不是 FactoryDe​​legate 来实现此模式? 这两种解决方案的优缺点是什么?

下面是一个理解我的意思的示例:

如果您有一个 Service 需要一个具有特定 ContextRepository,那么 服务构造函数需要一个工厂来创建或访问其存储库。

FactoryInterface方法

常见的解决方案是创建一个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(...);
    }
}

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

public MyEf4RepositoryFactory : IRepositoryFactory
{
    IRepository Create(ContextInformation context)
    {
        return new MyEf4Repository(context);
    }
}

并在应用程序中使用它:

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

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

FactoryDe​​legate 方法

除了 RepositoryFactoryInterface,您还可以使用需要更少编码的 FactoryDe​​legate 来完成相同的操作,如下所示

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(...);
    }
}

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

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

在我看来,FactoryDe​​legate context =>; new MyEf4Repository(context) 比 声明并实现接口 IRepositoryFactoryMyEf4RepositoryFactory

这一定是有原因的,我想知道为什么。

以下是使用接口方法的一个示例源:

container/1945023#1945023">询问后 15 个月 [更新]这个问题以及对 Java 世界的更多经验,我改变了主意:现在我更喜欢接口而不是委托。但我不能说为什么。这只是一种感觉。也许是因为我比较习惯?

Any place where you need a run-time value to construct a particular dependency, Abstract Factory is the solution.

My question is: Why do many sources favor FactoryInterface over FactoryDelegate to implement this pattern?
What are the pros and contras for both solutions?

Here is an example to understand what I mean:

If you have a Service that needs a Repository with a certain Context, then the Service constructor needs a factory to create or access its repository.

FactoryInterface approach

The common solution for this is to create a RepositoryFactoryInterface like this:

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(...);
    }
}

You also need to implement IRepositoryFactory interface some way...

public MyEf4RepositoryFactory : IRepositoryFactory
{
    IRepository Create(ContextInformation context)
    {
        return new MyEf4Repository(context);
    }
}

... and use it in the application:

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

----- End of mainstream solution ------

FactoryDelegate approach

Instead of the RepositoryFactoryInterface, you can do the same with a FactoryDelegate that requires less coding like this...

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(...);
    }
}

... and use it in the application:

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

In my opinion, the FactoryDelegate context => new MyEf4Repository(context) is much more compact than
declaring and implementing an interface IRepositoryFactory and MyEf4RepositoryFactory.

There must be a reason for this and I want to know why.

Here is one example source that uses the interface approach: answer to is-there-a-pattern-for-initializing-objects-created-via-a-di-container

[Update] 15 months after asking this question and having more experience with the Java universe, I changed my mind: Now I prefer interfaces over delegates. But I cannot say why. It is just a feeling. Maybe because I am more used to it?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

泼猴你往哪里跑 2024-11-01 12:14:45

任何需要运行时值来构造特定依赖项的地方,抽象工厂都是解决方案。

我反对这一点。不应使用运行时数据构建依赖关系,如此处所述。文章总结道:

不要在构建期间将运行时数据注入到应用程序组件中;它会导致歧义,使组合根复杂化并承担额外的责任,并使验证 DI 配置的正确性变得异常困难。相反,让运行时数据流过构造的对象图的方法调用。

当我们让运行时数据“流经构造对象图的方法调用”时,您将看到抽象工厂的用处衰退。当使用运行时数据从多个依赖项中进行选择时(与将运行时数据注入依赖项相比),它们可能仍然会被使用,但即使如此,抽象工厂通常也不是最好的解决方案,因为Cuttingedge.it/blogs/steven/pivot/entry.php?id=100" rel="nofollow noreferrer">此处解释。文章总结道:

一般来说,工厂抽象的使用并不是一种考虑其消费者的设计。根据依赖注入原则(DIP),抽象应该由它们的客户端定义,并且由于工厂增加了客户端被迫依赖的依赖项的数量,因此抽象显然不是为了客户端而创建的,因此我们可以认为这违反了 DIP。

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

这并不意味着您的应用程序中不能有生成依赖项的代码,但它不应该被定义为由其他应用程序组件使用的抽象。相反,类似工厂的行为应该封装到适配器中,这些适配器被定义为 组合的一部分根

当您只有这些类似工厂的逻辑和依赖项作为组合根的一部分时,无论您定义 IRepositoryFactory 还是仅仅使用 Func都并不重要> 构建这样的依赖关系,因为 IRepositoryFactory 也将在组合根中定义(因为应用程序没有使用这样的工厂的业务)。

也就是说,在极少数情况下,抽象工厂是正确的抽象(当您构建可重用框架时通常会发生这种情况),我确实发现工厂接口的使用比委托的使用更具启发性。它有点冗长,但更清楚这样的事情的含义。 IControllerFactoryFunc 更具意图性。

我想说,这对于那些不生成依赖项而是生成数据值的工厂来说更适用。以将 Func 注入构造函数为例。这实际上意味着什么以及它返回什么值?它返回 DateTime.Now 是否直观,或者返回 DateTime.Today 还是其他内容?在这种情况下,使用 GetCurrentTime() 方法定义 ITimeProvider 接口会更加清晰。

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

Any place where you need a run-time value to construct a particular dependency, Abstract Factory is the solution.

I would argue against this. Dependencies should not be constructed using runtime data, as explained here. In summary the article states:

Don't inject runtime data into application components during construction; it causes ambiguity, complicates the composition root with an extra responsibility and makes it extraordinarily hard to verify the correctness of your DI configuration. Instead, let runtime data flow through the method calls of constructed object graphs.

When we let runtime data "flow through the method calls of constructed object graphs" instead, you'll see the usefulness of Abstract Factories decline. They might still be used when runtime data is used to choose from multiple dependencies (compared to injecting runtime data into a dependency), but even then Abstract Factories are typically not the best solution, as explained here. In summary the article states:

Generally, the use of a factory abstraction is not a design that considers its consumers. According to the Dependency Injection Principle (DIP), abstractions should be defined by their clients, and since a factory increases the number of dependencies a client is forced to depend upon, the abstraction is clearly not created in favor of the client and we can therefore consider this to be in violation of the DIP.

Instead, patterns such as Facade, Composite, Mediator and Proxy are generally a better solution.

That doesn't mean you can't have code in your application that produces dependencies, but it should not be defined as abstraction that is used by other application components. Instead, factory-like behavior should be encapsulated into adapters that are defined as part of your Composition Root.

When you only have these factory-like logic and dependencies as part of your Composition Root, it doesn't really matter whether you define an IRepositoryFactory or merely use an Func<IRepository> to construct such dependency, since the IRepositoryFactory would be defined in the Composition Root as well (since the application has no business in using such factory).

That said, in the rare case that an Abstract Factory is the right abstraction (which will typically happen when you are building a reusable framework), I do find the use of factory interfaces much more intend revealing than the use of delegates. It is a bit more verbose, but much clearer what the meaning is of such a thing. An IControllerFactory is more intend revealing than Func<IController>.

I would say this even more holds for factories that do not produce dependencies but data values instead. Take for instance the example of injecting a Func<DateTime> into a constructor. What does this actually mean and what value does it return? Is it intuitive that it returns a DateTime.Now, or does it return DateTime.Today, or something else? In that case it would be much clearer to define an ITimeProvider interface with a GetCurrentTime() method.

NOTE: This answer was updated on July 2017 to reflect my latest views.

酒废 2024-11-01 12:14:45

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

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

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

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

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

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

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

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

Personally, I have always used the mainstream solution, simply because I didn't think of using a delegate.

After I thought of it, I faced the problem of separation of concerns. I'm using Ninject, and I didn't want my binding module to look like this (imagine the repositoy having some dependencies of itself):

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

That isn't readable at all. So I still used fully typed out abstract factories for Separation of Concern and readability.

Now I use the FuncModule described in this question (a la AutoFac). So I can do this:

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

and let ninject figure the dependencies out for me. As you can see, it's both more readable than using the method described above, and having to bind the factories for each dependency. This is the way I made the transition from the mainstream solution to the delegate solution.

So to answer your question. The reason I used the mainstream solution was because I didn't know how to do it another way at first (this is partly caused by most blogs fully typing out the abstract factories, can you see the circle?).

无妨# 2024-11-01 12:14:45

我认为将“工厂”称为代表不应该是正确的。

工厂负责实例化某些受支持的类型,例如某些存储库实现。

使用委托是没有意义的,因为每当您想要创建服务实例时,您都在定义如何实例化存储库。

我的问题是,如果您可以实现具有“新”和“类”约束的通用参数 TRepository,并且在构建时在服务内实例化存储库,为什么您应该使用委托?

这只是一个意见,但似乎你想要一条捷径,而不是一个更好的、设计良好的、最优的解决方案。

总结:

  • 委托工厂甚至允许工厂本身进行控制反转。

  • 委托对工厂的优势为零,因为您甚至可以消除委托本身的需要 - 也许,那么,它是无用或多余的 -。

  • 委托“工厂”不会是工厂,因为将实现 N 种创建存储库实例的方法,破坏了工厂的需求和优势。

  • 无需在使用者的代码中手动创建某些存储库的实例。只需使用通用参数,即可提供存储库类型,以便创建适当的实例,这要归功于控制反转。

I believe calling "factory" a delegate shouldn't be correct.

Factory has the responsability of instantiating some supported type, like some repository implementation.

Doing with delegates doesn't make sense because you're defining how your repository is instantiated whenever you want to create an instance of your service.

The question for me is why you should use a delegate if you can implement a generic parameter TRepository having "new" and "class" constraints, and, in construction time, instantiate the repository inside your service?

It's just an opinion, but it seems you want a shortcut instead of a better, well designed and optimal solution.

Summarizing:

  • Factory over a delegate allows inversion of control even for the factory itself.

  • Delegate over factory has zero advantage since you can even eliminate the need of a delegate itself - maybe, then, it's useless or redundant -.

  • Delegate "factory" won't be a factory because N ways of creating an instance of respository will be implemented, breaking the need and advantage of factory.

  • There's no need of manually create instances of some repository in the consumer's code. Just use a generic parameter so repository type can be provided in order to create an appropiate instance, thanks to inversion of control.

帅气尐潴 2024-11-01 12:14:45

我喜欢并使用委托解决方案,因为它更简洁。为了避免 Happy 先生提到的 IoC 容器的可读性问题,不要使用 Func。相反,创建您自己的命名委托。

delegate IRepository RepositoryFactory(ContextInformation context);

现在您可以两全其美:委托的简洁性和 IoC 容器的可读性。

I like and use the delegate solution as it is more concise. To avoid the readability problem with IoC containers that Mr. Happy mentioned, don't use Func. Instead create your own named delegate.

delegate IRepository RepositoryFactory(ContextInformation context);

Now you have the best of both worlds: the conciseness of delegates and the readability for IoC containers.

﹂绝世的画 2024-11-01 12:14:45

使用抽象工厂而不是简单工厂的全部目的是将各个工厂组合在一起。

就您而言,如果您需要委托来生成 IRepository 以外的任何内容,您就会遇到麻烦。

The whole point of using abstract factory over a simple factory is to group together individual factories.

In your case if you ever needed your delegate to produce anything other than an IRepository you would be in trouble..

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文