问题描述
我有一个在其中注入两个服务依赖项的类.我正在使用Unity容器.
I have a class where I am injecting two service dependencies. I am using Unity container.
public interface IOrganizer
{
void Method1();
void Method2();
void Method3();
}
public class Organizer : IOrganizer
{
private IService1 _service1;
private IService2 _service2;
public Organizer(Iservice1 service1, IService2 service2)
{
_service1 = service1;
_service2 = service2;
}
public void Method1()
{
/*makes use of _service1 and _service2 both to serve the purpose*/
}
public void Method2()
{
/*makes use of only _service1 to serve the purpose*/
}
public void Method3()
{
/*makes use of only _service2 to serve the purpose*/
}
}
这一切都可以,但是以某种方式闻起来是因为当我仅调用Method2
和Method3
时,统一会不必要地创建另一个不需要的服务的实例.此处的代码段只是用于说明目的的示例.在实际情况下,这些注入服务本身的对象图很深.
While it all works, but somehow it smells because when when I am only invoking Method2
and Method3
, unity unnecessarily creates an instance of another not required service. The code snippet here is just sample for explanation purpose. In real situation object graph of these injected services itself is quite deep.
是否有更好的方法来设计和解决这种情况?
Is there a better way to design and address this kind of scenario?
推荐答案
我认为您的嗅觉能胜任.大多数人都会很高兴地像这样编码,而无需再三思.不过,我确实同意,在OP中概述的设计中会有一些代码异味.
I think your sense of smell is competent. Most people would happily code like this without a second thought. I do agree, though, that there's a few code smells in a design like outlined in the OP.
我想指出的是,我在重构中使用了代码气味一词.一个>.这表明可能不正确,可能值得进一步研究.有时,这样的调查表明,代码按原样存在是有充分的理由的,您继续前进.
I'd like to point out that I use the term code smell as in Refactoring. It's an indication that something may not be right, and it might be worthwhile to investigate further. Sometimes, such investigation reveals that there are good reasons that the code is as it is, and you move on.
OP中至少有两种不同的气味.它们是无关的,所以我将分别对待它们.
There's at least two separate smells in the OP. They're unrelated, so I'll treat each one separately.
凝聚力是面向对象设计的一个基本但经常被遗忘的概念.可以将其视为对关注点分离的反作用力量.正如肯特·贝克(Kent Beck)曾经说过的那样(确切的来源使我无视,所以我将其解释为),一起变化的事物属于一起,而独立变化的事物则应该分开.
A fundamental, but often forgotten concept of object-oriented design is that of cohesion. Think of it as a counter-force to separation of concerns. As Kent Beck once put it (the exact source escapes me, so I paraphrase), things that vary together, belong together, while things that vary independently should be separated.
没有凝聚力的力量",关注点分离的力量"将把代码拆散,直到您拥有非常小的类,甚至简单的业务逻辑都散布在多个文件中.
Without the 'force' of cohesion, the 'force' of separation of concerns would pull code apart until you have extraordinarily small classes, and even simple business logic is spread across multiple files.
寻找内聚性或缺乏内聚性的一种方法是计算"一个类的每种方法正在使用多少个类字段.尽管只是粗略的指标,但它确实会在OP代码中触发我们的嗅觉.
One way to look for cohesion, or lack thereof, is to 'count' how many class fields are being used by each method of a class. While only a crude indicator, it does trigger our sense of smell in the OP code.
Method1
使用两个类字段,因此无需担心.另一方面,Method2
和Method3
都仅使用一半的类字段,因此我们可以将其视为凝聚力差的指示-如果可能的话,会产生代码气味.
Method1
uses both class fields, so is no cause for concern. Both Method2
and Method3
, on the other hand, use only half of the class fields, so we could view that as indication of poor cohesion - a code smell, if you will.
您该如何解决?同样,我想强调一点,不能保证气味难闻.这只是进行调查的原因.
How can you address that? Again, I wish to emphasise that a smell isn't guaranteed to be bad. It's only a reason to investigate.
不过,如果您想解决这个问题,除了将类分成几个较小的类之外,我别无选择.
Still, if you want to address the issue, I can't think of any other way than breaking up the class into several smaller classes.
OP中的Organizer
类实现了IOrganizer
接口,因此从技术上讲,只有在您也可以断开接口的情况下,才可以断开Organizer
-尽管您可以编写外观,然后将每个方法委派给实现该特定方法的单独类.
The Organizer
class in the OP implements the IOrganizer
interface, so technically, breaking up Organizer
is only possible if you can also break up the interface - although you could write a Facade, and then delegate each method to a separate class that implements that particular method.
仍然,接口的存在强调了接口隔离原则的重要性.我经常看到代码库会出现此特定问题,因为接口太大.尽可能使接口尽可能小.我倾向于将其发挥到极致,并在每个接口上仅定义一个成员.
Still, the presence of an interface emphasises the importance of the Interface Segregation Principle. I often see code bases exhibit this particular problem because the interfaces are too big. If at all possible, make the interfaces as small as possible. I tend to take it to the extreme and define only a single member on each interface.
从另一个 SOLID原则中,依赖关系倒置原则遵循,接口应该由使用它们的客户端而不是实现它们的类来定义.像这样设计界面通常可以使它们保持小巧而精巧.
From another of the SOLID principles, the Dependency Inversion Principle, follows that interfaces should be defined by the clients that use them, not the classes that implement them. Designing interfaces like that often enables you to keep them small, and to the point.
回想一下,单个类可以实现多个接口.
Recall also that a single class can implement multiple interfaces.
关于OP中的设计的另一个问题是性能,尽管我同意NightOwl888的评论,即您可能处于微优化领域.
Another concern regarding the design in the OP is of performance, although I agree with NightOwl888's comment that you're likely in micro-optimisation territory.
通常,您可以甚至可以自信地组成大型对象图.正如NightOwl888在上面的注释中还建议的那样,如果依赖项具有Singleton生存期,那么如果您注入它,但最终却没有使用它,则没有什么区别.
In general, you can compose even large object graphs with confidence. As NightOwl888 also suggests in the comments above, if a dependency has Singleton lifetime, it makes little difference if you inject it, but then end up not using it.
即使您不能提供像_service2
Singleton那样的依赖关系,我也再次同意NightOwl888的观点,.NET中的对象创建速度快到您几乎无法测量它的地步.正如他还指出的那样,注入构造函数应该很简单.
Even if you can't give a dependency like _service2
Singleton lifetime, I again agree with NightOwl888 that object creation in .NET is fast to the point where you almost can't measure it. And as he also points out, Injection Constructors should be simple.
即使在极少数情况下,依赖项必须具有Transient生存期,并且无论出于何种原因创建实例都是昂贵的,您始终可以将该依赖项隐藏在虚拟代理,正如我在有关对象图的文章中所描述的.
Even in the rare case where a dependency must have Transient lifetime, and for whatever reason creating an instance is expensive, you can always hide that dependency behind a Virtual Proxy, as I also describe in the article about object graphs.
如何在Unity中配置所有内容,我不再记得了,但是如果Unity无法处理,请选择另一种组合方法,最好纯DI .
How you configure all that in Unity, I no longer remember, but if Unity can't deal with that, choose another method of composition, preferably Pure DI.
这篇关于注入到构造函数中的依赖项的延迟初始化的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!