Я пытаюсь разобраться с концепцией Mixin, но не могу понять, что это такое.
На мой взгляд, это способ расширить возможности класса с помощью наследования.
Я читал, что люди называют их «абстрактными подклассами». Кто-нибудь может объяснить почему?
Буду признателен, если вы объясните свой ответ на следующем примере (из одного из моих слайд-шоу лекций):
Прежде чем вдаваться в подробности, полезно описать проблемы, которые он пытается решить. Скажем, у вас есть куча идей или концепций, которые вы пытаетесь смоделировать. Они могут быть связаны в некотором роде, но в большинстве своем они ортогональны, то есть они могут стоять независимо друг от друга. Теперь вы можете смоделировать это с помощью наследования, и каждая из этих концепций будет получена из некоторого общего класса интерфейса. Затем вы предоставляете конкретные методы в производном классе, который реализует этот интерфейс.
Проблема с этим подходом состоит в том, что этот дизайн не предлагает какого-либо четкого интуитивного способа взять каждый из этих конкретных классов и объединить их вместе.
Идея с дополнительными модулями состоит в том, чтобы предоставить набор примитивных классов, где каждый из них моделирует базовую ортогональную концепцию, и иметь возможность соединять их вместе, чтобы составлять более сложные классы с той функциональностью, которая вам нужна — что-то вроде legos. Сами примитивные классы предназначены для использования в качестве строительных блоков. Это расширяется, так как позже вы можете добавить другие примитивные классы в коллекцию, не затрагивая существующие.
Возвращаясь к C ++, техника для этого использует шаблоны и наследование. Основная идея здесь заключается в том, что вы соединяете эти строительные блоки вместе, предоставляя их через параметр шаблона. Затем вы соединяете их вместе, например. с помощью typedef
, чтобы сформировать новый тип, содержащий функциональность, которую вы хотите.
Возьмем ваш пример, скажем, мы хотим добавить функцию повтора поверх. Вот как это может выглядеть:
#include <iostream>
using namespace std;
struct Number
{
typedef int value_type;
int n;
void set(int v) { n = v; }
int get() const { return n; }
};
template <typename BASE, typename T = typename BASE::value_type>
struct Undoable : public BASE
{
typedef T value_type;
T before;
void set(T v) { before = BASE::get(); BASE::set(v); }
void undo() { BASE::set(before); }
};
template <typename BASE, typename T = typename BASE::value_type>
struct Redoable : public BASE
{
typedef T value_type;
T after;
void set(T v) { after = v; BASE::set(v); }
void redo() { BASE::set(after); }
};
typedef Redoable< Undoable<Number> > ReUndoableNumber;
int main()
{
ReUndoableNumber mynum;
mynum.set(42); mynum.set(84);
cout << mynum.get() << '\n'; // 84
mynum.undo();
cout << mynum.get() << '\n'; // 42
mynum.redo();
cout << mynum.get() << '\n'; // back to 84
}
Вы заметите, что я сделал несколько изменений по сравнению с вашим оригиналом:
value_type
для второго параметра шаблона, чтобы сделать его использование менее громоздким. Таким образом, вы не должны продолжать печатать <foobar, int>
каждый раз, когда ты соединяешь кусочек.typedef
используется.Обратите внимание, что это простой пример, иллюстрирующий идею объединения. Так что это не учитывает угловые случаи и забавные обычаи. Например, выполняя undo
без установки числа, вероятно, не будет вести себя так, как вы могли бы ожидать.
Как заметку, вы также можете найти Эта статья полезно.
Mixin — это класс, предназначенный для обеспечения функциональности другого класса, обычно через указанный класс, который предоставляет базовые функции, необходимые для этой функциональности. Например, рассмотрим ваш пример:
Миксин в этом случае обеспечивает функциональность отмены операции установки класса значений. Эта способность основана на get/set
функциональность, предоставляемая параметризованным классом ( Number
класс, в вашем примере).
Другой пример (Извлечено из «Смешанное программирование на C ++«):
template <class Graph>
class Counting: public Graph {
int nodes_visited, edges_visited;
public:
Counting() : nodes_visited(0), edges_visited(0), Graph() { }
node succ_node (node v) {
nodes_visited++;
return Graph::succ_node(v);
}
edge succ_edge (edge e) {
edges_visited++;
return Graph::succ_edge(e);
}
...
};
В этом примере миксин обеспечивает функциональность подсчет вершин, дан класс графа, который выполняет операции обхода.
Обычно в C ++ миксины реализуются через CRTP идиома. Этот поток может быть хорошим чтением о реализации mixin в C ++: Что такое C ++ Mixin-Style?
Вот пример миксина, который использует идиому CRTP (благодаря @Simple):
#include <cassert>
#ifndef NDEBUG
#include <typeinfo>
#endif
class shape
{
public:
shape* clone() const
{
shape* const p = do_clone();
assert(p && "do_clone must not return a null pointer");
assert(
typeid(*p) == typeid(*this)
&& "do_clone must return a pointer to an object of the same type");
return p;
}
private:
virtual shape* do_clone() const = 0;
};
template<class D>
class cloneable_shape : public shape
{
private:
virtual shape* do_clone() const
{
return new D(static_cast<D&>(*this));
}
};
class triangle : public cloneable_shape<triangle>
{
};
class square : public cloneable_shape<square>
{
};
Этот миксин обеспечивает функциональность гетерогенная копия к набору (иерархии) классов фигур.
Мне нравится ответ от великого волка, но я хотел бы предложить один пункт предостережения.
greatwolf заявил: «Виртуальные функции здесь действительно не нужны, потому что мы точно знаем, какой у нас составной тип класса во время компиляции». К сожалению, вы можете столкнуться с некоторым противоречивым поведением, если вы используете свой объект полиморфно.
Позвольте мне настроить основную функцию из его примера:
int main()
{
ReUndoableNumber mynum;
Undoable<Number>* myUndoableNumPtr = &mynum;
mynum.set(42); // Uses ReUndoableNumber::set
myUndoableNumPtr->set(84); // Uses Undoable<Number>::set (ReUndoableNumber::after not set!)
cout << mynum.get() << '\n'; // 84
mynum.undo();
cout << mynum.get() << '\n'; // 42
mynum.redo();
cout << mynum.get() << '\n'; // OOPS! Still 42!
}
Делая виртуальную функцию «set», будет вызвано правильное переопределение, и описанное выше противоречивое поведение не произойдет.
Миксины в C ++ выражаются с использованием Любопытно повторяющийся шаблон (CRTP). Эта почта это отличная разбивка того, что они предоставляют по сравнению с другими методами повторного использования … полиморфизм во время компиляции.
Это работает так же, как интерфейс и, может быть, больше как абстракция, но интерфейсы легче получить с первого раза.
Он решает многие проблемы, но одна, которую я нахожу в разработке, — это внешние apis. представь это.
У вас есть база данных пользователей, эта база данных имеет определенный способ получения доступа к своим данным.
Теперь представьте, что у вас есть Facebook, у которого также есть определенный способ получить доступ к своим данным (api).
в любой момент ваше приложение может быть запущено с использованием данных из Facebook или вашей базы данных. поэтому вы создаете интерфейс, который говорит: «все, что реализует меня, обязательно будет иметь следующие методы», теперь вы можете реализовать этот интерфейс в своем приложении …
Поскольку интерфейс обещает, что в репозиториях реализации будут методы, объявленные в них, вы знаете, что где бы и когда бы вы ни использовали этот интерфейс в своем приложении, если вы переключаете данные, у него всегда будут методы, которые вы определяете, и, следовательно, данные для работы.
В этом шаблоне работы есть еще много уровней, но суть в том, что он хорош, потому что данные или другие такие постоянные элементы становятся большой частью вашего приложения, и если они изменяются без вашего ведома, ваше приложение может сломаться 🙂
Вот немного псевдокода.
interface IUserRepository
{
User GetUser();
}
class DatabaseUserRepository : IUserRepository
{
public User GetUser()
{
// Implement code for database
}
}
class FacebookUserRepository : IUserRepository
{
public User GetUser()
{
// Implement code for facebook
}
}
class MyApplication
{
private User user;
MyApplication( IUserRepository repo )
{
user = repo;
}
}
// your application can now trust that user declared in private scope to your application, will have access to a GetUser method, because if it isn't the interface will flag an error.
Чтобы понять концепцию, забудьте о занятиях на мгновение. Подумайте (самый популярный) JavaScript. Где объекты — это динамические массивы методов и свойств. Может вызываться по имени в виде символа или строкового литерала. Как бы вы реализовали это в стандарте C ++ в 2018 году? Не легко. Но это суть концепции. В JavaScript можно добавлять и удалять (иначе говоря, встраивать) всякий раз, когда захотите. Очень важно: нет наследования классов.
Теперь на C ++. Стандартный C ++ имеет все, что вам нужно, здесь не помогает утверждение. Очевидно, я не буду писать язык сценариев для реализации смешивания с использованием C ++.
Да, Это хорошая статья , но только для вдохновения. КРТП не панацея. А также так называемый академический подход Вот, также (по существу) на основе CRTP.
Перед повторным голосованием этот ответ, возможно, рассмотрим мой p.o.c. код на коробке палочки 🙂