Почему нельзя использовать классы в качестве модулей? - PullRequest
0 голосов
/ 23 ноября 2018

Module является суперклассом Class:

Class.superclass
# => Module

В ООП это означает, что экземпляр Class может использоваться в любом месте, где может быть экземпляр Moduleused.

Удивительно, но это не относится к Class экземплярам в Ruby:

class C end
c = C.new

module M end
# Let's do all the extend/include/prepend stuff with M!
c.extend M
C.include M
C.prepend M

# All worked fine until this line.
# Let's turn to classes now!

# First, get a class to work with.
class C_as_M end
C_as_M.class.superclass
# => Module # yes, C_as_M is an instance of a Module child
C_as_M.is_a? Module
# => true   # yes, it is still a Module

# And now let's do the same extend/include/prepend stuff with C_as_M!

c.extend C_as_M
# => TypeError: wrong argument type Class (expected Module)
C.include C_as_M
# => TypeError: wrong argument type Class (expected Module)
C.prepend C_as_M
# => TypeError: wrong argument type Class (expected Module)

В чем причина нарушения этого принципа ООП?Почему нельзя использовать классы в качестве модулей?

Ответы [ 2 ]

0 голосов
/ 24 ноября 2018

Полагаю, тем временем я выяснил, почему классы нельзя использовать в качестве модулей в 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:

enter image description here

Это увеличивает сложность и неоднозначность, вызывает "Проблема с алмазом ":

  • Имеют ли экземпляры 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.

0 голосов
/ 23 ноября 2018

В ООП это означает, что экземпляр класса может использоваться в любом месте, где может использоваться экземпляр модуля.

Вы путаете под типы и sub классы , то есть подтип (что касается уточнения контрактов) и наследования (что касается повторного использования дифференциального кода).

В Ruby наследование создает подкласс, но неподтип.(На самом деле, «тип» - это понятие, которое существует только в голове программиста в Ruby.)

Class < Module является одним примером подкласса, который не является подтипом, и StringIO <: <code>IO является примером подтипа, который не является подклассом.

В чем причина нарушения этого принципа ООП?

Это не принцип ОО.Подтип и ОО полностью ортогональны.Существуют ОО-языки без подтипов, а также не-ОО-языки с подтипами.


Примечание: на самом деле было бы легко свернуть модули и классы в один.Они могут наследовать друг от друга так же, как классы наследуют от классов, так же, как классы наследуют от модулей.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...