我对依赖注入有疑问。
说我想创建一个类 称之为WebGetTask
WebGetTask需要依赖HttpService
错误的代码1 代码:
private HttpService httpService;
...
List<WebGetTask> list = new ArrayList<WebGetTask>();
for(...)
{
list.add(new WebGetTask(httpService));
}
...
确定。我知道这很糟糕,因为注入了httpService,但它从未使用过,除了在新的WebGetTask上创建
确定 糟糕的代码2 代码:
private WebGetTaskFactory webGetTaskFactory;
...
List<WebGetTask> list = new ArrayList<WebGetTask>();
for(...)
{
list.add(webGetTaskFactory.newTask());
}
...
我认为这更好,因为我们使用工厂 但... 但..
从我站立的地方来看, 我知道 在WebGetTaskFactory中 除了创建新的WebGetTask的唯一目的之外,我们仍在注入HttpService并且没有做任何事情所以 回顾一下 我的问题是 我如何设计一个工厂类(WebGetTaskFactory),当新对象在其构造函数上需要依赖项(HttpService)而不是简单地注入和传递依赖项(HttpService)时,它创建新对象(WebGetTask)? 或者更确切地说,这是这样做的方式吗?如果是这样,那么一切都很好,如果不是,那么请指导我如何正确使用DI和工厂模式。 感谢。
答案 0 :(得分:8)
我将假设您显示的代码是DownloadManager类的一部分,并且您通过构造函数注入依赖项。在这种情况下,我希望将所有内容粘合在一起的启动代码看起来像这样:
IHttpService httpService = new HttpService();
IWebGetTaskFactory webGetTaskFactory = new WebGetTaskFactory(httpService);
IDownloadManager downloadManager = new DownloadManager(webGetTaskFactory);
DownloadManager类只知道IWebGetTaskFactory接口。它不了解IHttpService,因此满足了Demeter的规律。
编辑:重新阅读您的问题之后,您似乎担心自己没有“使用”工厂中的HttpService,除非将其传递给新的WebGetTask。还行吧。 WebGetTaskFactory和WebGetTask都需要一个HttpService实例来完成它们的工作。这不违反得墨忒耳的法律。
答案 1 :(得分:3)
好的,在LoD下,关于传递一个实现对象,构造函数中的“插件”,没有什么特别的错误。重要的是,类的接口并没有告诉你很多关于的实现。
如果你的WebGetTask接口依赖于HttpService的确切实现,那么 违反了Demeter法则。
这里的诀窍是考虑WebGetTask的接口签名。这个名称本身表明你并不完全遵循得墨忒耳定律 - 或者知识最少的原则 - 因为你定义了一个类,(1)被定义为特定于网络,而(2)是一个动词而不是一个名词。
现在,这些都不一定是错的,但如果你愿意的话,它们都是“OO闻起来”,这表明你可能没有足够的对象思考。
让我们尝试“重构”设计。首先,考虑一个没有与之关联的“web”的GetTask。然后,您可以在构建时或稍后构建服务对象并将其传入。如果是HttpService,那很好,但是您的类的用户不需要任何有关内容的信息。
第二件事,让我们把它变成名词。称之为TaskFactory - 你的直觉引领着你 - 带着一个带有IOService的ctor,我刚刚将其发明为HttpService实现的抽象接口。
现在,你有(这是一种Java / C ++伪代码,不要对语法细节感到兴奋):
class Task { ... }
class TaskFactory {
public TaskFactory(IOServer svc){...}
public Task get(){...}
}
你可以通过写
来使用它 TaskFactory fac = new TaskFactory(new HttpService());
Task tsk = fac.get();
现在,我们知道关于TaskFactory,IO服务以及任务的内部问题的最低限度。
答案 2 :(得分:1)
DI有两种方式:第一种是构造函数,只有一个或两个对象被注入时才有用,而一个setter就是一个(实际上需要多少个setter)。
如果你想使用DI的工厂方法而不是基于构造函数的工厂方法。
示例1,对于构造函数DI:
list.add( new WebGetTask( httpService ) ) ;
示例2,对于设定者DI:
WebGetTask webGetTask = new WebGetTask();
webGetTask.setHttpService(httpService);
// set other dependencies
list.add(webGetTask);
当创建可能表现不同但具有相同接口的对象(即LoD)时需要使用更大的逻辑时,工厂方法最佳。让我们假设有一个基于工厂参数动态实现的DownloadManager接口。
示例3,封装到工厂方法中的创建逻辑:
public static DownloadManager createDownloadManager(HttpService httpService){
if(null!=httpService){
WebGetTask webGetTask = new WebGetTask();
webGetTask.setHttpService(httpService);
// set other dependencies
return new DownloadManagerImpl1(webGetTask);
} else {
return new DownloadManagerImpl2();
} // if-else
}