为什么更喜欢模板方法而不是依赖注入

Why prefer template method over dependency injection?

本文关键字:依赖 注入 更喜欢 模板方法 为什么      更新时间:2023-10-16

我一直在阅读Gamma等人的《设计模式》。我有一个关于模板方法和依赖注入的问题。

使用Template Method,您可以用策略"模板"类,这些策略为所需的操作或计算提供替代方案。因此,不是从几个备选策略中选择一个策略并将该策略编码到类中,而是允许类的用户指定他们想要使用的备选策略。

我听起来很有道理。但是我在概念上遇到了一点障碍。

如果你用策略对象实例化一个类,策略对象需要实现一个抽象接口。然后,程序员可以编写不同的策略,这些策略都不会出错地编译到类中,因为这些策略实现了接口。使用策略的类被编码到策略接口,而不是实现。

如果你要为这些策略对象定义一个抽象的IPolicy,为什么不直接使用依赖注入并在构建时传递IPolicy呢?

谁能解释一下为什么你更喜欢模板方法而不是依赖注入?

关于"模板方法"(而不是设计模式),下面的示例可能有助于决定该做什么。该示例创建一个库的详细模式,以帮助调试/开发。

与模板

struct console_print
{
  static void print(const string& msg) {std::cout<<msg;}
};
struct dont_print
{
  static void print(const string& msg) {}
};
template<printer>
void some_function()
{
  printer::print("some_function calledn");
}

库用户可以写:

some_function<console_print>(); //print the verbose message;
some_function<dont_print>();    //don't print any messages.

这段代码的好处是,如果用户不想打印代码,那么对dont_print::print(msg)的调用完全从代码中消失(空静态类很容易被优化掉)。这样的调试消息甚至可以输入到性能关键区域。

模板的缺点是需要在编译之前决定策略。你还需要修改你的模板的函数/类签名。

没有模板

当然可以这样做:

struct printer 
{
  virtual void print(const std::string& msg) = 0;
}
struct console_print : public printer
{
  void print(const std::string& msg) {std::cout<<msg;}
}
struct debug_print : public printer
{
  void print(const std::string& msg) {}
}

这样做的好处是,您可以将打印机类型传递给类和函数,并在运行时更改它们(对于某些应用程序可能非常有用)。然而,代价是总是对虚函数进行调用,因此空的dont_print确实有一个小代价。对于性能关键区域,这可能是可接受的,也可能是不可接受的。

首先,正如phresnel所提到的,模板方法模式不是现代意义上的模板。再读一遍,它使用运行时多态性来实现与STL算法使用编译时多态性(函数模板)相同的目标。

第二,从某种意义上说,所有类型的多态性都是依赖注入。也就是说,调用者将算法引入它所作用的具体类型。因此,问题通常不在于是否可以或应该使用依赖注入而不是使用其他模式:而是该模式显示了构建代码以使用依赖注入的有用方法。

如果你的"注入的依赖项"是一个模板类型参数,算法使用duck类型,你不需要实现一个抽象接口:只需编写具有预期签名的方法。

在你的例子中,"模板方法"(不要与模板方法模式混淆),或者让我们称之为"静态依赖注入",将避免对虚函数的需要。您主要通过向编译器提供更多明确的知识来获得性能,从而为他提供更好的优化机会。类变得更加静态,类型安全性得到提高。

类大小可能会缩小(不需要或减少需要存储指针)。

俗话说:

不要为你不用的东西付钱。

在这里也同样适用。如果你不需要虚拟接口,模板可以帮助你在不牺牲所有灵活性的情况下避免它们。