Наследование ruby против mixins
В Ruby, поскольку вы можете включить несколько микшинов, но только расширить один класс, кажется, что mixins будут предпочтительнее над наследованием.
Мой вопрос: если вы пишете код, который должен быть расширен/включен, чтобы быть полезным, почему бы вам сделать его классом? Или по-другому, почему бы вам не сделать это модулем?
Я могу думать только об одной причине, по которой вам нужен класс, и если вам нужно создать экземпляр класса. Однако в случае ActiveRecord:: Base вы никогда не создаете экземпляр непосредственно. Так не должен ли он быть модулем?
Ответы
Ответ 1
Я только что прочитал об этой теме в Хорошо обоснованный рубист (кстати, великая книга). Автор лучше объясняет, чем мог бы, поэтому я процитирую его:
Ни одно правило или формула всегда не приводит к правильному дизайну. Но полезно
пара соображений в виду, когда вы принимаете решения класса и модуля:
-
У модулей нет экземпляров. Из этого следует, что сущности или вещи в целом лучше всего
моделируется в классах, а характеристики или свойства объектов или вещей
наилучшим образом инкапсулированный в модули. Соответственно, как отмечено в разделе 4.1.1, класс
имена, как правило, являются существительными, тогда как имена модулей часто являются прилагательными (Stack
против Stacklike).
-
Класс может иметь только один суперкласс, но он может смешиваться с таким количеством модулей, сколько ему нужно. Если
вы используете наследование, отдавайте приоритет созданию разумного суперкласса/подкласса
отношения. Не используйте класс и одно только отношение суперкласса к
наделяйте класс тем, что может оказаться лишь одним из нескольких наборов характеристик.
Подводя итог этим правилам в одном примере, вот что вы не должны делать:
module Vehicle
...
class SelfPropelling
...
class Truck < SelfPropelling
include Vehicle
...
Скорее вы должны это сделать:
module SelfPropelling
...
class Vehicle
include SelfPropelling
...
class Truck < Vehicle
...
Вторая версия моделирует объекты и свойства гораздо более аккуратно. Грузовая машина
(что имеет смысл), в то время как SelfPropelling является характеристикой транспортных средств (по крайней мере, всех тех, кого мы волнуем в этой модели мира) - характеристика, которая передается грузовикам в силу того, что Truck является потомком или специализированный
формы, транспортного средства.
Ответ 2
Я думаю, что mixins - отличная идея, но здесь есть еще одна проблема, о которой никто не упомянул: конфликты пространства имен. Рассмотрим:
module A
HELLO = "hi"
def sayhi
puts HELLO
end
end
module B
HELLO = "you stink"
def sayhi
puts HELLO
end
end
class C
include A
include B
end
c = C.new
c.sayhi
Какой выигрыш? В Ruby оказывается последним, module B
, потому что вы включили его после module A
. Теперь легко избежать этой проблемы: убедитесь, что все константы и методы module A
и module B
находятся в маловероятных пространствах имен. Проблема в том, что компилятор вообще не предупреждает вас о том, когда происходят столкновения.
Я утверждаю, что это поведение не масштабируется для больших групп программистов - вы не должны предполагать, что человек, реализующий class C
, знает обо всех именах в области. Ruby даже позволит вам переопределить константу или метод другого типа. Я не уверен, что можно было бы считать правильным поведением.
Ответ 3
My take: Модули предназначены для совместного использования, а классы - для моделирования отношений между объектами. Вы технически могли бы просто сделать все экземпляром Object и смешать все модули, которые хотите получить желаемый набор поведения, но это будет плохой, случайный и довольно нечитаемый дизайн.
Ответ 4
Ответ на ваш вопрос во многом контекстуальный. Дистиллируя наблюдение за публикацией, выбор в основном определяется рассматриваемой областью.
И да, ActiveRecord должен был быть включен, а не расширен подклассом. Другой ORM - datamapper - точно достигает этого!
Ответ 5
Мне нравится, что Andy Gaskell очень много ответил - просто хотел добавить, что да, ActiveRecord не должен использовать наследование, а скорее включать модуль, чтобы добавить поведение (в основном постоянство) к модели/классу. ActiveRecord просто использует неправильную парадигму.
По той же причине мне очень нравится MongoId над MongoMapper, потому что он оставляет разработчику возможность использовать наследование как способ моделирования чего-то значимого в проблемной области.
Печально, что почти никто из сообщества Rails не использует "наследование Ruby", как он должен был использовать, - для определения иерархий классов, а не только для добавления поведения.
Ответ 6
Прямо сейчас, я думаю о шаблоне дизайна template
. Это просто не понравилось бы с модулем.