Когда Googled, появляется много ответов на эту тему. Однако я не чувствую, что кто-либо из них хорошо иллюстрирует разницу между этими двумя функциями. Поэтому я хотел бы попробовать еще раз, в частности ...
Что можно сделать с помощью самоподтипов, а не наследования, и наоборот?
Для меня должно быть какое-то количественное, физическое различие между ними, иначе они просто номинально отличаются.
Если Черта A расширяет B или самотипы B, разве они оба не иллюстрируют, что наличие B является требованием? В чем разница?
design-patterns
object-oriented-design
scala
Марк Канлас
источник
источник
Ответы:
Если черта A расширяет B, то смешивание в A дает вам точно B плюс все, что A добавляет или расширяет. Напротив, если черта A имеет собственную ссылку, которая явно указана как B, тогда родительский класс должен также смешиваться с B или с потомком B (и сначала смешивать его , что важно).
Это самое важное отличие. В первом случае точный тип B кристаллизуется в точке A, расширяющей его. Во втором случае разработчик родительского класса решает, какая версия B используется в точке, где создается родительский класс.
Другое отличие состоит в том, что A и B предоставляют методы с одинаковыми именами. Если A расширяет B, метод A переопределяет B. Когда A смешивается после B, метод A просто выигрывает.
Напечатанная ссылка на себя дает вам гораздо больше свободы; связь между A и B ослаблена.
ОБНОВИТЬ:
Поскольку вы не уверены в пользе этих различий ...
Если вы используете прямое наследование, вы создаете черту A, которая является B + A. Вы установили отношения в камне.
Если вы используете типизированную ссылку на себя, то любой, кто хочет использовать вашу черту A в классе C, может
И это не предел их опций, учитывая то, как Scala позволяет создавать экземпляр свойства непосредственно с помощью блока кода в качестве его конструктора.
Что касается разницы между выигрышем метода А , поскольку А смешивается последним, по сравнению с А, расширяющим В, рассмотрим это ...
Когда вы смешиваете в последовательности признаков, всякий раз, когда
foo()
вызывается метод , компилятор переходит к последней смешанной характеристике, чтобы искатьfoo()
, затем (если не найден) он проходит последовательность влево, пока не найдет характеристику, которая реализуетfoo()
и использует который. A также имеет опцию callsuper.foo()
, которая также проходит по последовательности влево, пока не найдет реализацию, и так далее.Так что, если A имеет типизированную собственную ссылку на B и автор A знает, что B реализует
foo()
, A может вызвать,super.foo()
зная, что, если ничего не предоставитfoo()
, B сделает. Тем не менее, создатель класса C имеет возможность отбросить любую другую черту, в которой реализованыfoo()
, и A получит это вместо этого.Опять же , это гораздо более мощные и менее жесткие ограничения , чем вытянутая B и прямой вызов версии B по
foo()
.источник
У меня есть некоторый код, который иллюстрирует некоторые различия между видимостью и реализацией «по умолчанию» при расширении против установки собственного типа. Он не иллюстрирует ни одну из обсуждаемых частей о том, как разрешаются реальные коллизии имен, но вместо этого сосредотачивается на том, что возможно и невозможно сделать.
Одним из важных отличий IMO является то, что
A1
он не раскрывает потребностиB
в чем-либо, что просто видит егоA1
(как показано на приведенных деталях). Единственный код, который фактически увидит, что используется конкретная специализация,B
- это код, который явно знает о составном типе (напримерThink*{X,Y}
).Другой момент заключается в том, что
A2
(с расширением) будет фактически использоваться,B
если ничего не указано, в то время какA1
(self-type) не говорит, что он будет использовать,B
пока не будет переопределено, конкретный B должен быть явно указан при создании экземпляров объектов.источник