在书的第11章"清洁代码:敏捷软件工艺手册"中,Bob叔叔说下面的懒惰初始化并不是一个干净的代码。它需要承担两个责任,并且具有很强的依赖性。
public Service getService() {
if (service == null)
service = new MyServiceImpl(...); // Good enough default for most cases?
return service;
}
除了IoC Container和Factory之外,有没有办法让代码干净并用依赖关系分开?
答案 0 :(得分:6)
此示例的用途是违反了Single Responsibility Principle和Dependency Inversion Principle。罗伯特马丁已经在例子之后说明了:
具有这两个职责意味着该方法正在进行 不止一件事,所以我们打破了单一责任 原理
他还通过陈述:
来触及依赖性倒置原则我们现在对
MyServiceImpl
以及所有内容都有一个硬编码依赖项 它的构造函数需要。
拥有这种硬编码的依赖意味着打破 依赖倒置原则。
此问题的解决方案与使用IoC容器或工厂无关。这里的解决方案是将依赖注入模式应用于:
为解决我们的专业问题制定全球一致的战略 的依赖关系。
如果我们应用Dependency Injection pattern,我们的课程会变得更简单,就像这样:
public class Consumer
{
private Service service;
public Consumer(Service service) {
this.service = service;
}
public void SomeMethod() {
// use service
}
}
请注意,Consumer
现在不再通过其公共方法公开Service
。这不应该是必需的,因为模块不应该共享其内部状态,如果某个其他组件需要使用我们的Service
,我们可以直接将它注入到其他组件中。
上面的示例似乎暗示我们在这里丢失了延迟初始化,但事实并非如此。我们只是将延迟初始化的责任转移到了“全局,一致的策略”,a.k.a。Composition Root。
由于Service
是一个抽象,我们可以创建一个只为我们的MyServiceImpl
实现延迟初始化的代理(延迟初始化将是它的唯一责任)。这样的代理可以看起来如下:
internal class LazyServiceProxy : Service
{
// Here we make use of .NET's Lazy<T>. If your platform doesn't
// have this, such type is easily created.
private Lazy<Service> lazyService;
public LazyServiceProxy(Lazy<Service> lazyService) {
this.lazyService = lazyService;
}
public void ServiceMethod() {
// Lazy initialization happens here.
Service service = this.lazyService.Value;
service.ServiceMethod();
}
}
这里我们创建了一个LazyServiceProxy
,其唯一目的是推迟创建真实服务。它甚至不需要“对MyServiceImpl
的硬编码依赖及其构造函数所需的一切”。
在我们的合成根中,我们可以轻松地将所有内容连接在一起,如下所示:
Service service = new LazyServiceProxy(
new Lazy<Service>(() => new MyServiceImpl(...)));
Consumer consumer = new Consumer(service);
在这里,我们将应用任何延迟初始化的责任移到了应用程序的启动路径上,我们保持或Consumer
(可能还有许多其他组件)清除了对Service
实现的了解成为重量级人物。这甚至阻止我们让Consumer
取决于第二个ServiceFactory
抽象。
不仅使Consumer
这个额外的工厂抽象更复杂,它在这种特定情况下打破了依赖性倒置原则,因为MyServiceImpl
是一个重量级对象的事实,是一个实现详细信息因此我们通过工厂抽象泄漏实现细节。这违背了依赖性倒置原则:
抽象不应该依赖于细节。
如您所见,此解决方案不需要IoC容器(如果您愿意,仍然可以使用IoC容器)并且不需要工厂。虽然工厂设计模式在应用依赖注入时仍然有效,但您会看到正确应用SOLID和依赖注入将大大减少使用工厂的需要。