Позвольте мне сразу сказать, что функции, которые мы обсудим здесь, являются чистым ядом, привнесенным в объектно-ориентированное программирование теми, кто отчаянно нуждался в лоботомии, как это предложил Дэвид Уэст в своей книге « Объектное мышление» . Эти функции имеют разные названия, но наиболее распространенными являются черты и миксины . Я серьезно не могу понять, как мы все еще можем назвать программирование объектно-ориентированным, когда оно обладает этими функциями .
Страх и ненависть в Лас-Вегасе (1998) Терри Гиллиам
Во-первых, вот как они работают в двух словах. Давайте использовать модули Ruby в качестве примера реализации. Скажем, у нас есть класс Book
:
1
2
3
4
5
|
class Book def initialize(title) @title = title end end |
Теперь мы хотим, чтобы класс Book
использовал статический метод (процедуру), который делает что-то полезное. Мы можем либо определить его в служебном классе и позволить Book
вызвать его:
01
02
03
04
05
06
07
08
09
10
|
class TextUtils def self.caps(text) text.split.map(&:capitalize).join( ' ' ) end end class Book def print puts "My title is #{TextUtils.caps(@title)}" end end |
Или мы можем сделать его еще более «удобным» и extend
наш модуль для прямого доступа к его методам:
01
02
03
04
05
06
07
08
09
10
11
|
module TextModule def caps(text) text.split.map(&:capitalize).join( ' ' ) end end class Book extend TextModule def print puts "My title is #{caps(@title)}" end end |
Это кажется хорошим, если вы не понимаете разницу между объектно-ориентированным программированием и статическими методами. Более того, если мы забываем о чистоте ООП на минуту, этот подход на самом деле выглядит менее читабельным для меня, хотя в нем меньше символов; Трудно понять, откуда происходит метод caps()
, когда он вызывается так же, как #{caps(@title)}
вместо #{TextUtils.caps(@title)}
. Ты не думаешь?
Миксины начинают играть свою роль лучше, когда мы их include
. Мы можем объединить их, чтобы построить поведение класса, который мы ищем. Давайте создадим два миксина. Первый будет называться PlainMixin
и будет печатать название книги таким, какой он есть, а второй будет называться CapsMixin
и будет использовать заглавные буквы:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
|
module CapsMixin def to_s super .to_s.split.map(&:capitalize).join( ' ' ) end end module PlainMixin def to_s @title end end class Book def initialize(title) @title = title end include CapsMixin, PlainMixin def print puts "My title is #{self}" end end |
Calling Book
без включенного миксина напечатает название так, как оно есть. Как только мы добавляем оператор include
, поведение to_s
переопределяется, и метод print
дает другой результат. Мы можем комбинировать миксины для получения необходимой функциональности. Например, мы можем добавить еще один, который будет сокращать заголовок до 16 символов:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
|
module AbbrMixin def to_s super .to_s.gsub(/^(.{ 16 ,}?).*$/m, '\1...' ) end end class Book def initialize(title) @title = title end include AbbrMixin, CapsMixin, PlainMixin def print puts "My title is #{self}" end end |
Я уверен, что вы уже понимаете, что у них обоих есть доступ к закрытому атрибуту @title
класса Book
. Они на самом деле имеют полный доступ ко всему в классе. Они буквально являются «кусочками кода», которые мы вводим в класс, чтобы сделать его более мощным и сложным. Что не так с этим подходом?
Это та же проблема, что и с аннотациями , DTO , геттерами и служебными классами — они разрывают объекты на части и размещают части функциональности в местах, где объекты их не видят.
В случае миксинов, функциональность находится в modules
Ruby, которые делают предположения о внутренней структуре Book
и далее предполагают, что программист все равно поймет, что находится в Book
после изменения внутренней структуры. Такие предположения полностью нарушают саму идею инкапсуляции .
Такая тесная связь между миксинами и частной структурой объекта ни к чему не приводит непонятный и трудный для понимания код.
Самыми очевидными альтернативами миксинам являются составные декораторы . Взгляните на пример, приведенный в статье :
1
2
3
4
5
6
7
|
Text text = new AllCapsText( new TrimmedText( new PrintableText( new TextInFile( new File( "/tmp/a.txt" )) ) ) ); |
Разве это не похоже на то, что мы делали выше с Ruby-миксинами?
Однако, в отличие от миксинов, декораторы оставляют объекты небольшими и связными, накладывая на них дополнительную функциональность. Миксины делают наоборот — они делают объекты более сложными и, благодаря этому, менее читаемыми и обслуживаемыми.
Я искренне верю, что они просто яд. Кто бы ни изобрел их, он был далек от понимания философии объектно-ориентированного дизайна.
Вы также можете найти эти посты интересными: вертикальная или горизонтальная декомпозиция ответственности ; Составное имя — это запах кода ; Градиенты неизменности ; Анти-паттерны в ООП ; Как неизменный объект может иметь состояние и поведение? ;
Ссылка: | Черты и миксины не являются ООП от нашего партнера JCG Егора Бугаенко в блоге About Programming . |