Полагаю, тем временем я выяснил, почему классы нельзя использовать в качестве модулей в Ruby.
Или, если быть более точным: почему классы не могут быть включены / добавлены .
Это та же самая причина, по которой Ruby не поддерживает множественное наследование:
, чтобы избежать двусмысленности / сложности в иерархии предков .
После краткого объяснения того, как множественноенаследование влияет на иерархию предков, я объясню, почему включение / добавление классов приведет к множественному наследованию и / или аналогичным сложностям через заднюю дверь.
При одиночном наследовании иерархия предковданного класса - просто цепочка.
цепочка может быть длинной или короткой, но это всегда просто линейная цепочка классов-предков:
File.ancestors
=> [File, IO, File::Constants, Enumerable, Object, Kernel, BasicObject]
Object.ancestors
=> [Object, Kernel, BasicObject]
BasicObject.ancestors
=> [BasicObject]
Так что поиск переменной или метода экземпляра просткак то: смотреть в текущем классе;если не найден, идите к следующему предку и посмотрите там;если не найден, перейдите к следующему предку и посмотрите там ...
При множественном наследовании иерархия предков может переходить.
Гипотетически задано
class A end
class B < A; end
class C < A; end
class D < B, C
end
производит следующий граф предков класса D:
Это увеличивает сложность и неоднозначность, вызывает "Проблема с алмазом ":
- Имеют ли экземпляры D общие переменные экземпляра в классе A, наследуемые через B и наследуемые через C?
- Если они не разделяют их,
нам нужен расширенный синтаксис для указания
, хотим ли мы получить доступ к переменной экземпляра в A через B или через C.
Ruby разработан, чтобы избежать этой сложности.Следовательно, нет множественного наследования по проекту.
Включение / добавление модулей
- это еще одно средство построения / манипулирования иерархией предков:
class MyBase < BasicObject; end
class C < MyBase; end
C.ancestors
=> [C, MyBase, BasicObject]
module IncludeMe end
C.include IncludeMe
C.ancestors
=> [C, IncludeMe, MyBase, BasicObject]
module PrependMe end
C.prepend PrependMe
C.ancestors
=> [PrependMe, C, IncludeMe, MyBase, BasicObject]
module Intermediate end
MyBase.include Intermediate
C.ancestors
=> [PrependMe, C, IncludeMe, MyBase, Intermediate, BasicObject]
Включение / добавление модулей только поддерживает цепочку предков в простой цепочке.
Ничего плохого не происходит.
Включение / добавление классов
Теперь представьте IncludeMe
, PrependMe
и Intermediate
были не модулями, а классами.
Для простоты я буду придерживаться только одного класса:
class PrependMe
def to_s
"Hello from prepended #{super}!"
end
end
Имейте в виду, что PrependMe
наследуется от Object
по умолчанию:
PrependMe.ancestors
# => [PrependMe, Object, Kernel, BasicObject]
Также обратите внимание на тот факт, что в Ruby невозможно создавать безосновные классы ( BasicObject - единственный безосновательный класс):
class BaselessClass < nil # Try it!
end # You'll fail.
# => TypeError: superclass must be a Class (NilClass given)
Итак, каждый класс X (кроме BasicObject ) имеет цепочку предков, состоящую как минимум из двух частей,
всегда заканчивая BasicObject :
X.ancestors
# => [X, ..., BasicObject]
# the shortest possible chain is [X, BasicObject]
# the usual chain is [X, ..., Object, Kernel, BasicObject]
Итак, что должна иерархия предковкак выглядит класс С после C.prepend PrependMe
?
C.ancestors
=> [PrependMe, Object, Kernel, C, MyBase, BasicObject] ?
=> [PrependMe, C, Object, Kernel, MyBase, BasicObject] ?
=> [PrependMe, C, MyBase, Object, Kernel, BasicObject] ?
=> [PrependMe, C, MyBase, BasicObject] ? # Object and Kernel omitted on purpose
Или иерархия предков должна даже ветвиться в PrependMe
в собственную ветвь для Object
?Со всеми последствиями проблемы с алмазом.
Можно разумно поспорить за каждый из этих вариантов.
Ответ зависит от того, что вы хотите увидеть в результате (c = C.new).to_s
.
Очевидно, что включение / добавление классов привносит двусмысленность и сложность, аналогичные или даже хуже, чем у множественного наследования.Как и в случае множественного наследования, Ruby намеренно избегает этого.
По-видимому, это причина, по которой включение / добавление классов запрещено в Ruby.