德米特对工厂模式和依赖注入的规律

时间:2009-04-27 00:57:57

标签: dependency-injection law-of-demeter

我对依赖注入有疑问。

说我想创建一个类 称之为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和工厂模式。 感谢。

3 个答案:

答案 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
}