Статьи

Черты и миксины не являются ООП

Позвольте мне сразу сказать, что функции, которые мы обсудим здесь, являются чистым ядом, привнесенным в объектно-ориентированное программирование теми, кто отчаянно нуждался в лоботомии, как это предложил Дэвид Уэст в своей книге « Объектное мышление» . Эти функции имеют разные названия, но наиболее распространенными являются черты и миксины . Я серьезно не могу понять, как мы все еще можем назвать программирование объектно-ориентированным, когда оно обладает этими функциями .

Страх и ненависть в Лас-Вегасе (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 .