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


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

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

6 63

6 ответов:

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

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

идея с mix-ins состоит в том, чтобы предоставить кучу примитивных классов, где каждый из них моделирует базовую ортогональную концепцию, и быть в состоянии склеить их вместе, чтобы составить более сложные классы только с той функциональностью, которую вы хотите-вроде 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 для второго шаблона param, чтобы сделать его использование менее громоздким. Таким образом, у вас нет чтобы продолжать печатать <foobar, int> каждый раз, когда вы вставляете кусок вместе.
  • вместо того, чтобы создавать новый класс, который наследует от частей, простой есть.

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

в качестве примечания вы также можете найти в этой статье полезная.

mixin-это класс, назначенный для обеспечения функциональности для другого класса, обычно через указанный класс, который предоставляет основные функции, необходимые для функциональности. Например, рассмотрим ваш пример:
Mixin в этом случае предоставляет функциональность отмены операции set класса value. Эта hability основана на get/set функциональность, предоставляемая параметризованным классом (Number класса, в вашем примере).

другой пример (взято из "программирование на основе Mixin в 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);
  }
... 
};

в этом примере mixin предоставляет функциональность подсчет вершин, учитывая класс графа, который выполняет операции trasversal.

обычно в C++ миксины реализуются через CRTP идиома. Этот поток может быть хорошим чтением о реализации mixin в C++:что такое C++ Mixin-стиль?

вот пример миксин, который использует идиому 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>
{
};

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

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

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

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

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

он решает многие проблемы,но один я нахожу в разработке, которая приходит много внешних API. представьте себе это.

у вас есть база данных пользователей, что база данных имеет определенный способ получения доступа к своим данным. теперь представьте, что у вас есть 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 можно добавлять и удалять (aka mix-in), когда и что угодно. Очень важно: нет наследования класса.

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

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

перед голосованием этот ответ возможно рассмотрим мой p.o.C. код на палочке box :)