Что такое миксин (как концепция)

Я пытаюсь разобраться с концепцией Mixin, но не могу понять, что это такое.
На мой взгляд, это способ расширить возможности класса с помощью наследования.
Я читал, что люди называют их «абстрактными подклассами». Кто-нибудь может объяснить почему?

Буду признателен, если вы объясните свой ответ на следующем примере (из одного из моих слайд-шоу лекций):
Пример C ++ Mixin

63

Решение

Прежде чем вдаваться в подробности, полезно описать проблемы, которые он пытается решить. Скажем, у вас есть куча идей или концепций, которые вы пытаетесь смоделировать. Они могут быть связаны в некотором роде, но в большинстве своем они ортогональны, то есть они могут стоять независимо друг от друга. Теперь вы можете смоделировать это с помощью наследования, и каждая из этих концепций будет получена из некоторого общего класса интерфейса. Затем вы предоставляете конкретные методы в производном классе, который реализует этот интерфейс.

Проблема с этим подходом состоит в том, что этот дизайн не предлагает какого-либо четкого интуитивного способа взять каждый из этих конкретных классов и объединить их вместе.

Идея с дополнительными модулями состоит в том, чтобы предоставить набор примитивных классов, где каждый из них моделирует базовую ортогональную концепцию, и иметь возможность соединять их вместе, чтобы составлять более сложные классы с той функциональностью, которая вам нужна — что-то вроде 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 без установки числа, вероятно, не будет вести себя так, как вы могли бы ожидать.

Как заметку, вы также можете найти Эта статья полезно.

107

Другие решения

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>
{
};

Этот миксин обеспечивает функциональность гетерогенная копия к набору (иерархии) классов фигур.

7

Мне нравится ответ от великого волка, но я хотел бы предложить один пункт предостережения.

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», будет вызвано правильное переопределение, и описанное выше противоречивое поведение не произойдет.

5

Миксины в C ++ выражаются с использованием Любопытно повторяющийся шаблон (CRTP). Эта почта это отличная разбивка того, что они предоставляют по сравнению с другими методами повторного использования … полиморфизм во время компиляции.

4

Это работает так же, как интерфейс и, может быть, больше как абстракция, но интерфейсы легче получить с первого раза.

Он решает многие проблемы, но одна, которую я нахожу в разработке, — это внешние 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.
0

Чтобы понять концепцию, забудьте о занятиях на мгновение. Подумайте (самый популярный) JavaScript. Где объекты — это динамические массивы методов и свойств. Может вызываться по имени в виде символа или строкового литерала. Как бы вы реализовали это в стандарте C ++ в 2018 году? Не легко. Но это суть концепции. В JavaScript можно добавлять и удалять (иначе говоря, встраивать) всякий раз, когда захотите. Очень важно: нет наследования классов.

Теперь на C ++. Стандартный C ++ имеет все, что вам нужно, здесь не помогает утверждение. Очевидно, я не буду писать язык сценариев для реализации смешивания с использованием C ++.

Да, Это хорошая статья , но только для вдохновения. КРТП не панацея. А также так называемый академический подход Вот, также (по существу) на основе CRTP.

Перед повторным голосованием этот ответ, возможно, рассмотрим мой p.o.c. код на коробке палочки 🙂

0
По вопросам рекламы [email protected]