2

我有一个关于依赖注入的问题。

说我想创建一个类,调用它,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 中我们仍在注入一个 HttpService 并且除了创建一个新的 WebGetTask 的唯一目的之外没有对它做任何事情

所以回顾一下我的问题是我如何设计一个工厂类(WebGetTaskFactory),当新对象在其构造函数上需要依赖项(HttpService)而不简单地注入和传递依赖项(HttpService)时创建新对象(WebGetTask)?或者更确切地说,这是这样做的方法吗?如果是这样,那么一切都很好,如果不是,那么请指导我如何正确使用DI和工厂模式。谢谢。

4

3 回答 3

9

我将假设您显示的代码是 DownloadManager 类的一部分,并且您通过构造函数注入依赖项。在这种情况下,我希望将所有内容粘合在一起的启动代码如下所示:

IHttpService httpService = new HttpService();
IWebGetTaskFactory webGetTaskFactory = new WebGetTaskFactory(httpService);
IDownloadManager downloadManager = new DownloadManager(webGetTaskFactory);

DownloadManager 类只知道 IWebGetTaskFactory 接口。它不知道IHttpService,从而满足得墨忒耳定律。

编辑:重新阅读您的问题后,您似乎担心您没有“使用”工厂中的 HttpService,除非将其传递给新的 WebGetTask。还行吧。WebGetTaskFactory 和 WebGetTask 都需要一个 HttpService 实例来完成它们的工作。这并不违反得墨忒耳定律。

于 2009-04-27T01:15:37.233 回答
3

好的,在 LoD 下传递一个实现对象(构造函数中的一个“插件”)并没有什么特别的错误。重要的是该类的接口并没有告诉您太多关于该实现的信息。

如果您的 WebGetTask 接口依赖于 HttpService 的确切实现,违反了得墨忒耳定律。

这里的技巧是考虑 WebGetTask 的接口签名。这个名字本身就表明你没有完全遵循得墨忒耳法则——或最少知识原则——因为你定义了一个类(1)被定义为特定于网络,而(2)是一个动词的一个名词。

现在,这些都不一定是错误的,但如果你愿意的话,它们都是“OO 气味”,这表明你可能没有足够客观地思考。

因此,让我们尝试“重构”设计。首先,考虑一个没有与之关联的“网络”的 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 服务和 Tasks 的内部结构。

于 2009-04-27T01:14:56.440 回答
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
}
于 2009-04-27T01:27:44.583 回答