问题描述
我正在考虑一个 C# 库的设计,它将具有几个不同的高级函数.当然,那些高级功能将尽可能使用 SOLID 类设计原则来实现.因此,可能会有供消费者定期直接使用的类,以及作为那些更常见的最终用户"类的依赖项的支持类".
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 - 尽管为一两个常见的 DI 库(StructureMap、Ninject 等)添加基本支持"似乎是合理的,但我希望消费者能够将该库与任何 DI 框架一起使用.
- 非 DI 可用 - 如果库的使用者不使用 DI,则该库仍应尽可能易于使用,从而减少用户创建所有这些不重要"依赖项所需的工作量获得他们想要使用的真实"类.
我目前的想法是为常见的 DI 库(例如 StructureMap 注册表、Ninject 模块)提供一些DI 注册模块",以及非 DI 并包含与这几个耦合的集合或工厂类工厂.
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.
想法?
推荐答案
一旦您了解 DI 是关于模式和原则,而不是技术,这实际上很容易做到.
This is actually simple to do once you understand that DI is about patterns and principles, not technology.
要以与 DI 容器无关的方式设计 API,请遵循以下一般原则:
To design the API in a DI Container-agnostic way, follow these general principles:
编程到接口,而不是实现
这个原则实际上是引用自 设计模式,但它应该始终是您的真正目标.DI 只是实现这一目标的一种手段.
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.
应用好莱坞原则
DI 术语中的好莱坞原则说:不要调用 DI 容器,它会调用你.
The Hollywood Principle in DI terms says: Don't call the DI Container, it'll call you.
切勿通过从代码中调用容器来直接请求依赖项.使用 Constructor Injection 隐式请求它.
Never directly ask for a dependency by calling a container from within your code. Ask for it implicitly by using 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; }
}
}
请注意 Service 类如何保证其不变量.一旦创建了实例,由于 Guard Clause 和 readonly
关键字的组合,可以保证依赖项可用.
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.
如果需要短期对象,请使用抽象工厂
使用构造函数注入注入的依赖项往往是长期存在的,但有时您需要一个短期对象,或者根据仅在运行时知道的值来构造依赖项.
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.
只在最后的责任时刻作曲
保持对象解耦直到最后.通常,您可以等待并将所有内容连接到应用程序的入口点.这称为组合根.
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.
更多细节在这里:
- 我应该在哪里使用 Ninject 进行注入2+(以及如何安排我的模块?)
- 设计 - 应该在哪里注册对象使用温莎时
使用外观进行简化
如果您觉得生成的 API 对于新手用户来说过于复杂,您可以随时提供一些 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.
要提供具有高度可发现性的灵活外观,您可以考虑提供 Fluent Builders.像这样的:
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);
}
}
这将允许用户通过编写来创建默认 Foo
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();
如果您想象 MyFacade 类封装了许多不同的依赖项,我希望很清楚它如何提供适当的默认值,同时仍然可以发现可扩展性.
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,在写完这个答案很久之后,我扩展了这里的概念并写了一篇关于 DI 友好库,以及一篇关于 DI 友好的框架.
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) “友好"图书馆的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持编程学习网!