依赖注入(DI)“友好”图书馆 [英] Dependency Inject (DI) "friendly" library

查看:121
本文介绍了依赖注入(DI)“友好”图书馆的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在思考一个C#库的设计,这将有几个不同的高级功能。当然,这些高级职能将使用 SOLID 类设计原则尽可能多地实现。因此,可能会有一些课程旨在让消费者定期直接使用,以及支持类,这些类别是更常见的最终用户类的依赖。



问题是,设计图书馆的最佳方式是什么?




  • DI不可知 - 虽然添加了基本的支持对于一个或两个常见的DI库(StructureMap,Ninject等)似乎是合理的,我希望消费者能够使用任何DI框架的库。

  • 非DI可用 - 如果图书馆的消费者不使用DI,图书馆应尽可能简单易用,减少用户创建所有这些不重要依赖的工作量,只是为了获得真实类他们想要使用。



我目前的想法是为通用DI库提供几个DI注册模块(例如StructureMap注册表,Ninject模块)以及非DI a的set或Factory类



想法?

解决方案

一旦你明白DI是关于模式和原则,而不是技术,这实际上很简单。



要在DI中设计API容器无关的方式,请遵循以下一般原则:



程序到界面而不是实现



这个原则实际上是来自设计模式的引用(来自内存) ,但它应该始终是您的真实目标



应用好莱坞原则



DI条款中的好莱坞原则说:不要拨打DI Container,它会打给你



不要直接通过从代码中调用容器来请求依赖关系。通过使用构造函数注入隐含地请求它。



使用构造方法注入



当您需要依赖关系时,通过构造函数静态请求

  public class服务:IService 
{
private readonly ISomeDependency dep;

public Service(ISomeDependency dep)
{
if(dep == null)
{
throw new ArgumentNullException(dep);
}

this.dep = dep;
}

public ISomeDependency依赖关系
{
get {return this.dep; }
}
}

请注意Service类如何保证其不变量。一旦创建了一个实例,由于Guard条款和只读关键字的组合,依赖关系被保证可用。



如果您需要一个短命的对象,请使用抽象工厂



使用构造函数注入注入的依赖关系往往是长期存在的,但是有时您需要一个短命的对象,或者根据运行时已知的值构造依赖关系。



请参阅



仅在最后的责任时刻撰写



保持对象解耦直到最后。通常,您可以等待并连接应用程序的入口点。这被称为构成根



更多细节在这里:





简化使用Facade



如果您觉得最终的API对于新手用户来说太复杂,您可以随时提供一些 Facade 课程封装了常见的依赖组合。



为了提供灵活的Facade具有高度的可发现性,您可以考虑提供Fluent Builders。这样的东西:

  public class MyFacade 
{
private IMyDependency dep;

public MyFacade()
{
this.dep = new DefaultDependency();
}

public MyFacade WithDependency(IMyDependency dependency)
{
this.dep =依赖;
返回这个;
}

public Foo CreateFoo()
{
返回新的Foo(this.dep);
}
}

这将允许用户创建一个默认的Foo写作

  var foo = new MyFacade()。CreateFoo(); 

然而,这是可以发现的,可以提供一个自定义的依赖关系,你可以写

  var foo = new MyFacade()。WithDependency(new CustomDependency())。CreateFoo() 

如果你想像MyFacade类封装了很多不同的依赖项,我希望很清楚提供适当的默认值,同时仍然可扩展性可发现。






FWIW,在写这个答案后,我扩展了这里的概念,写了一篇关于 DI友好图书馆的更长的博客文章,以及关于 DI-Friendly Frameworks 的随附文章。


I'm pondering the design of a C# library, that will have several different high level functions. Of course, those high-level functions will be implemented using the SOLID class design principles as much as possible. As such, there will probably be classes intended for consumers to use directly on a regular basis, and "support classes" that are dependencies of those more common "end user" classes.

The question is, what is the best way to design the library so it is:

  • DI Agnostic - Although adding basic "support" for one or two of the common DI libraries (StructureMap, Ninject, etc) seems reasonable, I want consumers to be able to use the library with any DI framework.
  • Non-DI usable - If a consumer of the library is using no DI, the library should still be as easy to use as possible, reducing the amount of work a user has to do to create all these "unimportant" dependencies just to get to the "real" classes they want to use.

My current thinking is to provide a few "DI registration modules" for the common DI libraries (e.g a StructureMap registry, a Ninject module), and a set or Factory classes that are non-DI and contain the coupling to those few factories.

Thoughts?

解决方案

This is actually simple to do once you understand that DI is about patterns and principles, not technology.

To design the API in a DI Container-agnostic way, follow these general principles:

Program to an interface, not an implementation

This principle is actually a quote (from memory though) from Design Patterns, but it should always be your real goal. DI is just a means to achieve that end.

Apply the Hollywood Principle

The Hollywood Principle in DI terms says: Don't call the DI Container, it'll call you.

Never directly ask for a dependency by calling a container from within your code. Ask for it implicitly by using Constructor Injection.

Use Constructor Injection

When you need a dependency, ask for it statically through the constructor:

public class Service : IService
{
    private readonly ISomeDependency dep;

    public Service(ISomeDependency dep)
    {
        if (dep == null)
        {
            throw new ArgumentNullException("dep");
        }

        this.dep = dep;
    }

    public ISomeDependency Dependency
    {
        get { return this.dep; }
    }
}

Notice how the Service class guarantees its invariants. Once an instance is created, the dependency is guaranteed to be available because of the combination of the Guard Clause and the readonly keyword.

Use Abstract Factory if you need a short-lived object

Dependencies injected with Constructor Injection tend to be long-lived, but sometimes you need a short-lived object, or to construct the dependency based on a value known only at run-time.

See this for more information.

Compose only at the Last Responsible Moment

Keep objects decoupled until the very end. Normally, you can wait and wire everything up in the application's entry point. This is called the Composition Root.

More details here:

Simplify using a Facade

If you feel that the resulting API becomes too complex for novice users, you can always provide a few Facade classes that encapsulate common dependency combinations.

To provide a flexible Facade with a high degree of discoverability, you could consider providing Fluent Builders. Something like this:

public class MyFacade
{
    private IMyDependency dep;

    public MyFacade()
    {
        this.dep = new DefaultDependency();
    }

    public MyFacade WithDependency(IMyDependency dependency)
    {
        this.dep = dependency;
        return this;
    }

    public Foo CreateFoo()
    {
        return new Foo(this.dep);
    }
}

This would allow a user to create a default Foo by writing

var foo = new MyFacade().CreateFoo();

It would, however, be very discoverable that it's possible to supply a custom dependency, and you could write

var foo = new MyFacade().WithDependency(new CustomDependency()).CreateFoo();

If you imagine that the MyFacade class encapsulates a lot of different dependencies, I hope it's clear how it would provide proper defaults while still making extensibility discoverable.


FWIW, long after writing this answer, I expanded upon the concepts herein and wrote a longer blog post about DI-Friendly Libraries, and a companion post about DI-Friendly Frameworks.

这篇关于依赖注入(DI)“友好”图书馆的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆