Я изучаю миксины (в C ++). Я прочитал несколько статей о миксинах и обнаружил две разные модели «аппроксимирующих» миксинов в C ++.
Образец 1:
template<class Base>
struct Mixin1 : public Base {
};
template<class Base>
struct Mixin2 : public Base {
};
struct MyType {
};
typedef Mixin2<Mixin1<MyType>> MyTypeWithMixins;
Шаблон 2: (может называться CRTP)
template<class T>
struct Mixin1 {
};
template<class T>
struct Mixin2 {
};
struct MyType {
};
struct MyTypeWithMixins :
public MyType,
public Mixin1<MyTypeWithMixins>,
public Mixin2<MyTypeWithMixins> {
};
Они практически эквивалентны? Я хотел бы знать практическое различие между образцами.
Разница в видимости. В первом паттерне MyType
Участники непосредственно видны и могут использоваться миксинами, без необходимости кастинга, и Mixin1
Участники видны Mixin2
, Если MyType
хочет получить доступ к участникам из миксинов, ему нужно разыграть this
и нет лучшего способа сделать это безопасно.
Во втором шаблоне нет автоматической видимости между типом и миксинами, но миксины могут безопасно и легко разливаться. this
в MyTypeWithMixins
и тем самым получить доступ к членам типа и других mixins. (MyType
может тоже, если вы применили CRTP к нему тоже.)
Так что все сводится к удобству и гибкости. Если ваши миксины имеют простой доступ к сервисам типа и не имеют собственных зависимых элементов, первый шаблон хорош и прост. Если миксин зависит от услуг, предоставляемых типом или другими миксинами, вы более или менее вынуждены использовать второй шаблон.
Они практически эквивалентны? Я хотел бы знать практическое различие между образцами.
Они разные концептуально.
Для первого шаблона у вас есть декораторы, которые (прозрачно) обращаются к базовому классу функциональности, каждый из которых добавляет свой собственный поворот / специализацию к существующей реализации.
Взаимосвязь первых моделей моделей является «is-a» (MyTypeWithMixins
это Mixin1<MyType>
специализации, Mixin1<MyType>
это MyType
специализации).
Это хороший подход, когда вы реализуете функциональность в жестком интерфейсе (как и все типы будет реализовывать тот же интерфейс).
Для второго шаблона у вас есть функциональные части, используемые в качестве деталей реализации (возможно, в разных, не связанных классах).
Смоделированные здесь отношения «реализованы в терминах» (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 { // knows/implements C's API
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
сохраните свои настройки с номером версии, но они никоим образом не находятся в одной и той же иерархии объектов (у них нет общей базы).