Dependency injection 工厂模式和依赖注入的德米特定律

Dependency injection 工厂模式和依赖注入的德米特定律,dependency-injection,law-of-demeter,Dependency Injection,Law Of Demeter,我有一个关于依赖注入的问题 假设我想创建一个类 叫它吧,WebGetTask WebGetTask需要依赖于HttpService 错误代码1 代码: 私有HttpService-HttpService; ... 列表=新的ArrayList(); 对于(…) { 添加(新的WebGetTask(httpService)); } ... 嗯。我知道这很糟糕,因为httpService是注入的,但除了在新的WebGetTask上创建之外,它从未被使用过 嗯 错误代码2 代码: 私有W

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

假设我想创建一个类 叫它吧,WebGetTask

WebGetTask需要依赖于HttpService

错误代码1 代码:

私有HttpService-HttpService;
...   
列表=新的ArrayList();
对于(…)
{   
添加(新的WebGetTask(httpService));
}
...
嗯。我知道这很糟糕,因为httpService是注入的,但除了在新的WebGetTask上创建之外,它从未被使用过

嗯 错误代码2 代码:

私有WebGetTaskFactory WebGetTaskFactory;
...  
列表=新的ArrayList();
对于(…)
{   
add(webGetTaskFactory.newTask());
}  
...
我认为这样更好,因为我们使用工厂 但是 但是

从我的立场来看, 我看得出来 在WebGetTaskFactory中 我们仍然在注入一个HttpService,除了创建一个新的WebGetTask之外,没有对它做任何事情

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

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

如果您的WebGetTask接口依赖于HttpService的精确实现,则违反了Demeter定律

这里的技巧是考虑WebGetTask的接口签名。这个名字本身表明,你没有完全遵循德米特定律——或最少知识的原则——因为你定义的类(1)被定义为特定于网络的,以及(2)是动词而不是名词

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

因此,让我们尝试“重构”设计。首先,考虑一个没有“web”关联的GetTask。然后,您可以在构建时或稍后构建一个服务对象并将其传入。如果是HttpService,那没关系,但是类的用户不需要任何关于隐藏内容的信息

第二件事,让我们把它变成一个名词。用一个接受IOService的ctor将其称为TaskFactory,我刚刚发明了一个由HttpService实现的抽象接口

现在,您有了(这是一种Java/C++伪代码,不要对语法细节感到兴奋):

你通过写作来使用它

 TaskFactory fac = new TaskFactory(new HttpService());
 Task tsk = fac.get();

现在,我们对TaskFactory、IO服务以及其他任务的内部了解最少。

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

IHttpService httpService = new HttpService();
IWebGetTaskFactory webGetTaskFactory = new WebGetTaskFactory(httpService);
IDownloadManager downloadManager = new DownloadManager(webGetTaskFactory);
DownloadManager类只知道IWebGetTaskFactory接口。它不了解IHTTP服务,因此符合德米特定律


编辑:重读您的问题后,您似乎担心您没有在工厂“使用”HttpService,只是为了将其传递给新的WebGetTask。这没关系。WebGetTaskFactory和WebGetTask都需要一个HttpService实例来完成它们的工作。这并不违反德米特定律。

DI有两种方式:第一种是构造函数,当只注入一个或两个对象时,它是有用的;第二种是setter(实际上,根据需要有尽可能多的setter)

如果您想为DI使用工厂方法,原则上它与基于构造函数的方法相同

示例1,对于构造函数DI:

list.add( new WebGetTask( httpService ) ) ;
示例2,对于setter 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
}

对担心没有在webGetTaskFactory中“使用”httpService。因此,我将“httpService”对象注入到“webGetTaskFactory”中,“webGetTaskFactory”仅在创建新的“webGetTask”时使用“httpService”。这没关系。酷。有没有更多的“教科书”的例子?或者,这是创建需要对其构造函数具有特定依赖关系的对象的最常用方法吗?如果您仔细想想,工厂会执行“运行时依赖关系注入”。如果您希望遵循在运行时动态创建的对象的依赖项注入模式,而不是设计时,这是一种自然的方式。
list.add( new WebGetTask( httpService ) ) ;
WebGetTask webGetTask = new WebGetTask();
webGetTask.setHttpService(httpService);
// set other dependencies
list.add(webGetTask);
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
}