Несколько раз от коллег звучал вопрос о том, что не понятно, зачем вообще нужны mixin'ы (примеси) в языке Dart. Я решил посмотреть, что есть на просторах Интернета по этому вопросу. К великому огорчению в статьях, которые удалось найти в основном говорится о том, как использовать примеси, но не дается разъяснений зачем они нужны, в каких случаях их использование более предпочтительно, чем обычное наследование или реализация интерфейсов. Эта статья является попыткой восполнить этот пробел. Несмотря на то, что в Интернете есть достаточно статей по теме примесей в Dart и Flutter, ясности они не вносят на мой взгляд потому, что приводимые примеры показывают чистую механику конструирования классов с примесями, которая далека от разумности и поэтому не демонстрирует реальную область их применения. В частности я встретил такой пример. Имеем: И почему-то нам захотелось получить животное, которое имеет свойства кошек и собак одновременно. В этом случае мы можем сделать так: К данному примеру есть по крайней мере два вопроса: При этом, зачем же все-таки нужны примеси, остается загадкой. По моему скромному убеждению, чтобы понять смысл примесей нужно начать рассмотрение вопроса с отношения наследования. Основной смысл наследования в ООП заключается в том, что одна сущность является разновидностью другой сущности. Например, Прямоугольник является разновидностью Фигуры или Кошка является разновидностью Животного. И именно это должно быть определяющим фактором при выстраивании иерархии классов. Если посмотреть на наследование с иной точки зрения, то увидим, что Прямоугольник наследует свойства Фигуры, а Кошка наследует свойства Животного. Если не обращать внимания на логику, то чисто технически может захотеться унаследовать свойства нескольких разных сущностей. Для этого некоторые языки програмирования поддерживают множественное наследование. Множественное наследование критикуется за ряд недостатков (см. Википедию), поэтому многие языки программирования не используют множественное наследование совсем, а используют механизм реализации интерфейсов и/или примеси. Да и, с точки зрения логики, конструкции, получающиеся в результате множественного наследования, не просто осмыслить. Для понимания следующего материала необходимо вспомнить некоторые понятия из элементарной логики. В частности понятия существенных и несущественных свойств. Существенными свойствами объектов являются те, благодаря наличию которых он отностится к тому или иному классу объектов. Несущественными свойствами объекта являются те, наличие, отсутствие или конкретные значения которых не влияет на принадлежность обекта некоторому классу объектов. Например, форма прямоугольника является существенным свойством этой фигуры, так как если мы изменим эту форму (уберем или добавим сторону или изменим углы), то прямоугольник перестанет быть прямоугольником. Но если изменить размеры прямоугольника, то он все равно останется прямоугольником. Поэтому размеры — это несущественное свойство. Выстраивание иерархии классов обычно основывается на добавлении к классу родителя каких-либо существенных свойств. Например, В основе этой иерархии лежит существенное свойство форма фигуры. Еще пример: Здесь существенным свойством является назначение виджета. А теперь предположим нам понадобилось добавить некоторое несущественное свойство к нашим сущностям. Таким свойством, например, является цвет. Пусть мы хотим теперь раскрашивать некоторые фигуры и виджеты. Для этого можно, конечно, воспользоваться наследованием и реализовать сначала классы PaintableShape и PaintableWidget. Но это оказывается не удобным, так как, во-первых, нам придется дублировать реализацию функционала раскрашивания в обеих иерархиях, а, во-вторых, для каждой фигуры и виджета, которые мы хотим раскрашивать, нам придется реализовывать новые классы, например, PaintableRect и PaintableContainer. Можно воспользоваться механизмом реализации интерфейсов. Тогда мы получим что-то типа такого: Как видно это тоже не самое лучшее решение, так как нам приходится дублировать один и тот же код для каждой раскаршиваемой сущности. Но все эти проблемы решаются, если фукционал, связанный с несущественным свойством, мы вынесем в отдельную примесь (mixin): Теперь можно этим пользоваться: Подводя итог вышесказанному, можно следующим образом определить, когда удобно использовать примеси: если имеется несколько различных иерархий, которым нужно добавить один и тот же функционал, определяющий некоторое несущественное свойство для сущностей этих иерархий. Либо это может быть одна иерархия, но мы имеем дело с разными ее ветками. В качестве примера рассмотрим виджеты фреймворка Flutter. Предположим нам понадобилось добавлять к некоторым виджетам функционал, связанный с одним и тем же свойством. Виджеты во Flutter конструируются следующим образом: или Для добавления некоторого свойства через наследование придется реализовывать по крайней мере два класса: при этом, как видно снова приходится дублировать функционал, связанный с добавляемым свойством. При использовании примесей проблема решается: Для тех, кто знаком с паттеранами проектирования, использование примесей в некоторых случаях может заменить применение паттерна Мост. В заключение следует заметить, что подобным образом можно подмешивать функционал сразу нескольких различных свойств в произвольных сочетаниях. Данная статья не претендует на исчерпывающее определение вариантов использования примесей. Вероятно пытливый ум разработчика сможет найти еще много красивых применений для них. Буду рад, если эти варианты использования примесей появятся в комментариях к этой статье. Программист