Comments 23
В статье мало наркомании)
А так было интересно.
Можно и то, и другое. Например, вот так:
abstract class Animal {
void run() {}
}
mixin SpeedRunner on Animal {
void speedRun() {
run();
run();
}
}
class Ostrich extends Animal with SpeedRunner {}
// Ostrich().speedRun();
Только в котлине или расте для этого не надо создавать новый тип как в данном случае. Можно имплементировать extension или trait сразу для любого типа, например, числового и вызвать метод: 1.foo()
. В целом миксины похожи на них, но с уклоном в обычное множественно наследование и не такие могущественные.
Миксины же о дополнении поведения и контракта изнутри, при этом так же позволяют использовать много методов и инкапсулировать их с данными миксина.
Ну наверное это ближе к растовым трейтам и хаскелевым тайпклассам. Но только в них нельзя хранить данные. Хотя имеет ли это большой смысл на самом деле? В таком случае можно просто разрешить множественное наследование классов в языке и не париться.
Множественное наследование тянет за собой diamond problem, так что от него уже все шугаются. Плюс не всегда нужна семантика is-a, миксины хорошо ложатся на текущий курс партии — composition over inheritance.
Когда полезно иметь данные в миксине — например в геймдеве. Если хочется чтобы объект имел свойства 'Serializable', 'Drawable', 'InventoryOwning' и 'Killable' (и ещё много чего), то реализовывать через множественное наследование — разлапистая иерархия получается, и изменять потом сложно. Легче набрать объект из миксинов, тем более что тесное взаимодействие с основным классом будет наоборот вредить гибкости.
Вышеперечисленные свойства похожи на обычные интерфейсы.
Похожи немного, да; ключевая разница — они с реализациями подмешиваются, так что мы сразу даём классу дополнительное обобщённое поведение (а не обязуемся новое специализированное поведение в классе реализовать). Взять в пример тот же Drawable — он может притащить с собой члены данных с графикой (которую мы можем назначить для каждого экземпляра свою) и код для отрисовки, а код класса к которому подмешиваем останется нетронут и даже не узнает, что класс внезапно научился себя отображать.
В Dart есть экстеншены:
extension SuperInt on int {
List<int> to(int end) => List.generate(end - this + 1, (i) => this + i);
}
После этого можно делать так:
final range = 2.to(5); // range = [2, 3, 4, 5]
gist.github.com/kevmoo/b60dc2fc7ea49acecb1fd2b57bf9be57
К слову, вы пытались привести пример из флаттера, так вот в самом движке есть намного более яркий пример SingleTickerProviderStateMixin. Он как раз выносит важный функционал для воспроизведения анимаций в отдельный миксин, хотя используется этот миксин классамм с общим родителем, там даже ограничение стоит — «on State». Так что, по-моему скромному мнению, чтобы восполнить пробел существующих статей, нужно было сначала изучить их поподробнее, ну и естественно посмотреть реальные кейсы, которыми сам Flutter более чем богат.
Прошу прощения, если я Вас обидел
Моя цель была не в том, чтобы разобрать тысячи вариантов использования примесей. Серди этих тысяч есть и хорошие и плохие. Моя задача была показать, в каких случаях использование примесей дает преимущество перед наследованием и интерфейсами. А если мы говорим о критике, то я рад критике, но критика бывает разной: конструктивной и деструктивной. При конструктивной критике, когда говорят, что есть ошибка, то показывают, в чем она. При деструктивной — просто говорят, что все не правильно без всяких пояснений. Вы сказали, что описанные мной варианты применения примесей ошибочны, но не показали, в чем именно эта ошибка. И тон Вашего обращения агрессивен, потому и был сделан вывод, что Вы обиделись на то, что Ваш пример был подвергнут критике (замечу, что конструктивной, так как было указано, что в этом примере не так). Если у Вас есть конструктивные замечания, то я готов их услышать. Если же их нет, то лучше закончить эту дискуссию.
в каких случаях их использование более предпочтительно, чем обычное наследование или реализация интерфейсов. Эта статья является попыткой восполнить этот пробел.
В итоге вы приводите всего 1 пример и делаете вывод:
если имеется несколько различных иерархий, которым нужно добавить один и тот же функционал, определяющий некоторое несущественное свойство для сущностей этих иерархий. Либо это может быть одна иерархия, но мы имеем дело с разными ее ветками.
То есть вынесение важной части функционала, которая по факту одинакова у обоих элементов вы считаете не возможным?
Киллер и биатлонист, оба стреляют у обоих это важное свойство, невозможно юзать миксин?
Слишком абстрактно?
Я привел пример, который вы судя по всему даже не открыли, раз пишите что я не представляю вам конструктивной критики.
Вы указали как тег своей статьи Флаттер в том числе. Наверное реализации в самом Флаттере тоже могут быть аргументом в таком случае.
Я привел вам пример SingleTickerProviderStateMixin это довольно важная часть функционала по реализации анимаций, и это миксин можно прицепить к различным стейтам.
Анимация для визуального класса не значимая вещь?
И да, я не предлагаю все подряд выносить в миксины, это то еще зло в красивой обертке. Любым инструментом надо пользоваться аккуратно и к месту, можно и фугасом попытаться гвоздь забить, может получиться. А может и нет.
Следующий момент, касаемо целей — вы поставили цель восполнить пробел, ну так показали бы разные кейсы, а то выходит кейс 1 со спорным выводом. Так и позиционировали бы статью как один из кейсов который мне кажется правильным.
А по поводу критики моего примера, я и не приводил его как вариант реального использования или великолепной архитектуры, я приводил его как абстрактный пример, для демонстрации конкретной ситуации, то что вы этого не увидели и захотели в нем видеть что-то другое, это как говорил один футболист ваши ожидания и ваши проблемы.
На счет киллера и биатлониста Вы абсолютно правы: они оба стреляют. И то, что они стреляют является существенным свойством обоих. Поэтому, если бы мне нужно было реализовывать это, то я бы воспользовался наследованием или реализацией интерфейсов (в зависимости от потребностей):
abstract class Shooter {
void shoot();
}
class Killer extends Shooter {}
class Biathlonist extends Shooter {}
И именно то, что ни киллер, ни биатлонист без стрельбы не могут существовать, говорит о том, что использование примесей в данном случае не является обоснованным. Примесь хороша тогда, когда объект остается самим собой, независимо от того, есть свойство, реализованное в примеси или нет.
Также не нужно путать важное свойство и существенное свойство. Свойство может быть важным, но не существенным. В частности в приведенном Вами примере с SingleTickerProviderStateMixin. Этот mixin безусловно добавляет важное свойство к виджету, но не существенное, так как при наличии или отсутствии этого свойства виджет остается виджетом. А вот если у виджета убрать или добавить существенное свойство, то он либо перестанет быть виджетом вообще, либо станет принципиально другим виджетом (примером принципиально разных виджетов являются StatelessWidget и StatefulWidget)
Поэтому приведенный мной кейс работает и в данном случае. И к нему следует относится не как к единственному частному случаю, а как к некоторому обобщению, в рамки которого укладываются все варианты использования примесей (ну или почти все).
А касаемо примера из Флаттера, ну вы бы хотя бы открыли что ли его для приличия. Ну право, он ограничение на State имеет, ну причем здесь виджеты. И юзается в куче его наследников, в том числе те которые напрямую относятся к стейтам для которых анимация не важное а существенное свойство, например ImplicitlyAnimatedWidgetState. Это даже из названия следует. И в таких для которых это не является существенным свойством, например _HelperErrorState. Видимо команде флаттера надо как можно скорее переписывать эту часть, ведь она не покрывается вашим случаем на все случаи жизни.
Вы очень любите примеры крайне далекие от реальной разработки. Поэтому я не считаю необходимым разгребать все, что Вы написали и тратить время на пустую дискуссию.
ну причем здесь виджеты
Виджеты здесь при том, что речь идет о состоянии виджета.
К сожалению вместо конструктивного обсуждения Вы опять скатились на раздраженно-снисходительный тон, который считаю неуместным в подобных обсуждениях. Уверяю Вас, что у меня достаточный опыт разработки, чтобы суметь обосновать сказанное в статье. Но с Вашей стороны я не вижу потребности это понять, а у меня нет никакого желания общаться в таком тоне. Поэтому со своей стороны я заканчиваю эту дискуссию. Пусть каждый остается при своем мнении.
Но с Вашей стороны я не вижу потребности это понять
Зря Вы считаете, что я не понимаю то что вы написали, у меня тоже достаточно опыта реальной разработки. И я не пытаюсь доказать что пример ваш не правилен, я пытался Вам показать, что возведение в абсолют чего-либо это неправильная позиция, и узкость. Но увы, видимо Вы не имеете нужды это увидеть, Вам в своём мирке уютно, и ничего кроме него Вы не хотите видеть. Это Ваше право. На этом всё, в одном мы сошлись — каждый при своём.
Использование примесей (mixins) в Dart