Наследование 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. Это просто не понравилось бы с модулем.