Object Behavior Must Not Be Configurable

The following text is a partial translation of the original English article, performed by ChatGPT (gpt-3.5-turbo) and this Jekyll plugin:

Использование свойств объектов в качестве параметров конфигурации - очень распространенная ошибка, которую мы продолжаем делать, в основном потому, что наши объекты изменяемы - мы настраиваем их. Мы изменяем их поведение, внедряя параметры или даже целые объекты настроек/конфигурации в них. Нужно ли мне сказать, что это злоупотребление и неуважение с философской точки зрения? Я могу, но давайте рассмотрим это с практической точки зрения.

Предположим, что есть класс, который должен читать веб-страницу и возвращать ее содержимое:

Кажется простым и прямолинейным, верно? Да, это довольно связанный и надежный класс. Вот как мы его используем для чтения содержимого главной страницы Google:

Все хорошо, пока мы не начинаем делать этот класс более мощным. Допустим, мы хотим настроить кодировку. Мы не всегда хотим использовать "UTF-8". Мы хотим, чтобы это было настраиваемым. Вот что мы делаем:

Готово, кодирование инкапсулировано и настраиваемо. Теперь предположим, что мы хотим изменить поведение класса для случая пустой страницы. Если загружается пустая страница, мы хотим вернуть "<html/>". Но не всегда. Мы хотим, чтобы это было настраиваемым. Вот что мы делаем:

Класс становится больше, да? Здорово, мы хорошие программисты, и наш код должен быть сложным, верно? Чем сложнее, тем лучше программисты мы! Я ироничен. Конечно, нет! Но давайте продолжим. Теперь мы хотим, чтобы наш класс все равно продолжал работу, даже если кодировка не поддерживается на текущей платформе:

Класс растет и становится все более мощным! Теперь пришло время представить новый класс, который мы назовем PageSettings:

Класс PageSettings в основном является хранилищем параметров без какого-либо поведения. Он имеет методы-геттеры, которые позволяют получить доступ к параметрам: isEncodeAnyway(), isAlwaysHtml() и getEncoding(). Если продолжать двигаться в этом направлении, то в этом классе может быть несколько десятков настроек конфигурации. Это может выглядеть очень удобно и является очень типичным шаблоном в мире Java. Например, посмотрите на JobConf из Hadoop. Вот как мы будем называть нашу высококонфигурируемую Page (предполагается, что PageSettings является неизменяемым).

Однако, несмотря на то, что это может показаться удобным на первый взгляд, такой подход очень неправильный. В основном потому, что он побуждает нас создавать большие и несвязные объекты. Они становятся больше по размеру и менее тестируемыми, менее поддерживаемыми и менее читаемыми.

Чтобы предотвратить это, я предлагаю простое правило: поведение объекта не должно быть настраиваемым. Или, более технически, инкапсулированные свойства не должны использоваться для изменения поведения объекта.

Свойства объекта существуют только для координации местоположения реального объекта, который представляет этот объект. uri - это координата, а свойство alwaysHtml типа boolean - это триггер изменения поведения. Видите разницу?

Итак, что же мы должны делать вместо этого? Какой правильный дизайн? Мы должны использовать композицию декораторов. Вот как:

Вот как будет выглядеть наш DefaultPage (да, мне пришлось немного изменить его дизайн):

Как видите, я делаю его реализацию интерфейса Page. Теперь декоратор TextPage, который преобразует массив байтов в текст с использованием заданной кодировки:

Сейчас NeverEmptyPage:

И, наконец, AlwaysTextPage:

Вы можете сказать, что AlwaysTextPage будет делать два вызова к инкапсулированному origin в случае неподдерживаемой кодировки, что приведет к дублированию HTTP-запроса. Это верно и это сделано специально. Мы не хотим, чтобы происходил дублированный HTTP-запрос. Давайте введем еще один класс, который будет кэшировать загруженную страницу (не является потокобезопасным, но это сейчас не важно).

Теперь наш код должен выглядеть так (обратите внимание, я сейчас использую OncePage):

Это, вероятно, самый кодоемкий пост на этом сайте до сих пор, но я надеюсь, что он читаемый и я смог передать идею. Теперь у нас есть пять классов, каждый из которых достаточно маленький, легко читаемый и простой в повторном использовании.

Просто следуйте правилу: никогда не делайте классы конфигурируемыми!

Translated by ChatGPT gpt-3.5-turbo/42 on 2023-12-27 at 13:51

sixnines availability badge   GitHub stars