它们在实践中是否等效?我想知道这两种方法之间的实际差异。
它们在概念上是不同的。 第一种模式中,装饰器会在核心功能类上(透明地)进行操作,每个装饰器都会向现有实现添加自己的扭曲/专业化。
第一种模式所建立的关系是"is-a"(MyTypeWithMixins是Mixin1 < MyType > 的特化,Mixin1 < MyType >是MyType的特化)。 当您在严格的接口内实现功能时,这是一种很好的方法(因为所有类型将实现相同的接口)。
对于第二种模式,您有作为实现细节的功能部分(可能位于不同的、不相关的类中)。 这里建模的关系是"is implemented in terms of"(MyTypeWithMixins是MyType的特化,使用Mixin1和Mixin2的功能来实现)。 在许多CRTP实现中,CRTP模板基础被继承为私有或受保护的。
当您在不同的、不相关的组件中实现常见功能时,这是一种很好的方法(即不具有相同接口的组件)。 这是因为从Mixin1继承的两个类将没有相同的基类。
为每个提供一个具体的例子:
对于第一种情况,请考虑GUI库的建模。 每个可视控件都将具有(例如)一个"display"函数,在ScrollableMixin中,如果需要,它会添加滚动条; 如果所有这些控件都是“控件/可视组件/可显示”的类层次结构的一部分,则滚动条mixin将成为大多数可调整大小的控件的基类。
class control {
virtual void display(context& ctx) = 0;
virtual some_size_type display_size() = 0;
};
template<typename C>class scrollable<C>: public C {
virtual void display(context& ctx) override {
if(C::display_size() > display_size())
display_with_scrollbars(ctx);
else
C::display(canvas);
}
...
};
using scrollable_messagebox = scrollable<messagebox>;
在这种情况下,所有混入类型都将覆盖(例如)一个显示方法,并将其功能的部分(专门的绘图部分)委托给装饰类型(基本类型)。
对于第二种情况,考虑一种情况,当您实现一个内部系统来为应用程序中的序列化对象添加版本号时。实现如下:
template<typename T>class versionable<T> { // doesn't know/need T's API
version_type version_
protected:
version_type& get_version()
}
class database_query: protected versionable<database_query> {}
class user_information: protected versionable<user_information> {}
在这种情况下,
database_query
和
user_information
都存储了带有版本号的设置,但它们不在同一个对象层次结构中(它们没有共同的基类)。