ВУЗ: Не указан
Категория: Не указан
Дисциплина: Не указана
Добавлен: 06.11.2023
Просмотров: 894
Скачиваний: 6
ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.
370 ции, использующие эти данные, обычно изменяются вместе, но бывают исключения. Наталкиваясь на такие исключения, мы перемещаем функ- ции, чтобы изменения осуществлялись в одном месте.
8.3.8. Группы данных
Часто в программе одни и те же три-четыре элемента данных попа- даются в множестве мест: поля в паре классов, параметры в нескольких сигнатурах методов. Связки данных, встречающихся совместно, надо превращать в самостоятельный класс. Сначала следует найти, где эти группы данных встречаются в качестве полей. Применяя к полям выде- ление метода, нужно преобразовать эти группы данных в класс. Затем обратить внимание на сигнатуры методов и применить Введение гра-
ничного объекта (Introduce Parameter Object) или Сохранение всего объ-
екта (Preserve Whole Object ), чтобы сократить их объем. В результате сразу удается укоротить многие списки параметров и упростить вызов методов.
Не стоит беспокоиться, что некоторые группы данных используют лишь часть полей нового объекта. Для проверки можно удалить одно из значений данных и посмотреть, сохранят ли при этом смысл остальные.
Если нет, это верный признак того, что данные напрашиваются на объе- динение их в объект. Сокращение списков полей и параметров несо- мненно улучшает код. После создания классов можно поискать завист- ливые функции и обнаружить методы, которые желательно переместить в образованные классы.
8.3.9. Одержимость элементарными типами
В большинстве программных сред есть два типа данных. Тип «за- пись» позволяет структурировать данные в значимые группы. Элемен- тарные типы данных служат стандартными конструктивными элемен- тами. С записями всегда связаны некоторые накладные расходы. Они могут представлять таблицы в базах данных, но их создание может ока- заться неудобным, если они нужны лишь в одном-двух случаях.
Один из ценных аспектов использования объектов заключается в том, что они затушевывают или вообще стирают границу между прими- тивными и большими классами. Нетрудно написать маленькие классы, неотличимые от встроенных типов языка. В Java есть примитивы для чисел, но строки и даты, являющиеся примитивами во многих других средах, суть классы.
Те, кто занимается ООП недавно, обычно неохотно используют ма- ленькие объекты для маленьких задач. В качестве примера можно при- вести, например, денежные классы, соединяющие численное значение и валюту, специальные строки типа телефонных номеров и почтовых индексов. Выйти в мир объектов помогает рефакторинг Замена значе-
ния данных объектом (Replace Data Value with Object) для отдельных значений данных. Когда значение данного является кодом типа, можно
371 обратиться к рефакторингу Замена кода типа классом (Replace Type
Code with Class), если значение не воздействует на поведение.
Если есть условные операторы, зависящие от кода типа, может по- дойти Замена кода типа подклассами (Replace Type Code with
Subclasses) или Замена кода типа состоянием/ стратегией (Replace
Type Code with State / Strategy).
При наличии группы полей, которые должны находиться вместе, можно применить выделение класса. Увидев примитивы в списках па- раметров, можно воспользоваться Введением граничного объекта
(Introduce Parameter Object). Если обнаружится разборка на части мас- сива, можно попробовать Замену массива объектом ( Replace Array with
Object).
8.3.10. Операторы типа switch
Одним из признаков объектно-ориентированного кода служит срав- нительная немногочисленность операторов типа switch (или case). Про- блема, обусловленная применением switch, по существу, связана с дуб- лированием. Часто один и тот же блок switch оказывается разбросанным по разным местам программы. При добавлении в переключатель нового варианта приходится искать все эти блоки switch и модифицировать их.
Понятие полиморфизма в ООП предоставляет элегантный способ спра- виться с этой проблемой.
Как правило, заметив блок switch, следует подумать о полиморфиз- ме. Задача состоит в том, чтобы определить, где должен происходить полиморфизм. Часто переключатель работает в зависимости от кода типа. Необходим метод или класс, хранящий значение кода типа. По- этому нужно воспользоваться выделением метода для выделения пере- ключателя, а затем перемещением метода для вставки его в тот класс, где требуется полиморфизм. В этот момент следует решить, чем вос- пользоваться – заменой кода типа подклассами или заменой кода типа состоянием/стратегией. Определив структуру наследования, можно применить Замену условного оператора полиморфизмом (Replace
Conditional with Polymorphism).
Если есть лишь несколько вариантов переключателя, управляющих одним методом, и не предполагается их изменение, то применение по- лиморфизма оказывается чрезмерным. В данном случае хорошим выбо- ром будет Замена параметра явными методами (Replace Parameter with
Explicit Method). Если одним из вариантов является null, можно попро- бовать прибегнуть к Введению объекта Null (Introduce Null Object).
8.3.11. Параллельные иерархии наследования
Параллельные иерархии наследования в действительности являются особым случаем «стрельбы дробью». В данном случае всякий раз при порождении подкласса одного из классов приходится создавать под- класс другого класса. Признаком этого служит совпадение префиксов
372 имен классов в двух иерархиях классов. Общая стратегия устранения дублирования состоит в том, чтобы заставить экземпляры одной иерар- хии ссылаться на экземпляры другой. С помощью перемещения метода и перемещения поля иерархия в ссылающемся классе исчезает.
8.3.12. Ленивый класс
Чтобы сопровождать каждый создаваемый класс и разобраться в нем, требуются определенные затраты. Класс, существование которого не окупается выполняемыми им функциями, должен быть ликвидиро- ван. Часто это класс, создание которого было оправданно в свое время, но уменьшившийся в результате рефакторинга. Либо это класс, добав- ленный для планировавшейся модификации, которая не была осуществ- лена. В любом случае следует дать классу возможность умереть. При наличии подклассов с недостаточными функциями можно попробовать
Свертывание иерархии (Collapse Hierarchy). Почти бесполезные компо- ненты должны быть подвергнуты Встраиванию класса (Inline Class).
8.3.13. Теоретическая общность
Эффект такого кода возникает, когда говорят о том, что в будущем, наверное, потребуется возможность делать такие вещи, и хотят обеспе- чить набор механизмов для работы с вещами, которые не нужны. То, что получается в результате, труднее понимать и сопровождать. Если бы все эти механизмы использовались, их наличие было бы оправданно, в противном случае они только мешают, поэтому лучше от них изба- виться.
Если есть абстрактные классы, не приносящие большой пользы, от нужно них избавиться путем сворачивания иерархии. Ненужное делеги- рование можно устранить с помощью встраивания класса. Методы с неиспользуемыми параметрами должны быть подвергнуты Удалению
параметров (Remove Parameter). Методы со странными абстрактными именами необходимо переименовать путем Переименования метода
(Rename Method).
Теоретическая общность может быть обнаружена, когда единствен- ными пользователями метода или класса являются контрольные приме- ры. Найдя такой метод или класс, нужно удалить его и контрольный пример, его проверяющий. Если есть вспомогательный метод или класс для контрольного примера, осуществляющий разумные функции, его, конечно, надо оставить.
8.3.13. Временное поле
Иногда обнаруживается, что в некотором объекте атрибут устанав- ливается только при определенных обстоятельствах. Такой код труден для понимания, поскольку естественно ожидать, что объекту нужны все его переменные. Трудно понять для чего существует некоторая пере-
373 менная, когда не удается найти, где она используется. С помощью вы- деления класса можно создать класс и поместить туда весь код, рабо- тающий с этими переменными. Возможно, удастся удалить условно вы- полняемый код с помощью введения объекта Null для создания альтер- нативного компонента в случае недопустимости переменных.
Часто временные поля возникают, когда сложному алгоритму тре- буются несколько переменных. Тот, кто реализовывал алгоритм, не хо- тел пересылать большой список параметров, поэтому он разместил их в полях. Но поля действенны только во время работы алгоритма, а в дру- гом контексте лишь вводят в заблуждение. В таком случае можно при- менить выделение класса к переменным и методам, в которых они тре- буются. Новый объект является объектом метода.
8.3.13. Цепочки сообщений
Цепочки сообщений появляются, когда клиент запрашивает у одного объекта другой, у которого клиент запрашивает еще один объект, у ко- торого клиент запрашивает еще один объект и т. д. Это может выгля- деть как длинный ряд методов getThis или последовательность времен- ных переменных. Такие последовательности вызовов означают, что клиент связан с навигацией по структуре классов. Любые изменения промежуточных связей означают необходимость модификации клиента.
Здесь применяется прием Сокрытие делегирования (Hide Delegate).
Это может быть сделано в различных местах цепочки. В принципе, можно делать это с каждым объектом цепочки, что часто превращает каждый промежуточный объект в посредника. Обычно лучше посмот- реть, для чего используется конечный объект. Можно попробовать с помощью выделения метода взять использующий его фрагмент кода и путем перемещения метода передвинуть его вниз по цепочке. Если не- сколько клиентов одного из объектов цепочки желают пройти осталь- ную часть пути, добавьте метод, позволяющий это сделать.
8.3.14. Посредник
Одной из главных характеристик объектов является инкапсуляция – сокрытие внутренних деталей от внешнего мира. Инкапсуляции часто сопутствует делегирование. Однако это может завести слишком далеко.
Например, рассмотрев интерфейс класса, обнаруживаем, что половина методов делегирует обработку другому классу. Тут надо воспользовать- ся Удалением посредника (Remove Middle Man) и общаться с объектом, который действительно знает, что происходит. При наличии нескольких методов, не выполняющих большой работы, с помощью встраивания метода следует поместите их в вызывающий метод. Если есть дополни- тельное поведение, то с помощью Замены делегирования наследованием
(Replace Delegation with Inheritance) можно преобразовать посредника в подкласс реального класса. Это позволит расширить поведение, не го- нясь за всем этим делегированием.
374 8.3.15. Неуместная близость
Иногда классы оказываются в слишком близких отношениях и чаще, чем следовало бы, погружены в закрытые части друг друга. Классы должны следовать строгим правилам. Чрезмерно взаимосвязанные клас- сы нужно разводить с помощью перемещения метода и перемещения поля, разделить части и уменьшить близость. Следует посмотреть, нель- зя ли прибегнуть к Замене двунаправленной связи однонаправленной
(Change Bidirectional Association to Unidirectional). Если у классов есть общие интересы, можно воспользоваться выделением класса, чтобы поместить общую часть в надежное место и превратить их в добропоря- дочные классы. Либо можно воспользоваться сокрытием делегирования, позволив выступить в качестве связующего звена другому классу.
К чрезмерной близости может приводить наследование. Подклассы всегда знают о своих родителях больше, чем последним хотелось бы.
Если пришло время расстаться с домом, примените Замену наследова-
ния делегированием (Replace Inheritance with Delegation).
8.3.16. Альтернативные классы с разными интерфейсами
В этих случаях можно использовать переименование метода ко всем методам, выполняющим одинаковые действия, но различающимся сиг- натурами. Часто этого оказывается недостаточно. В таких случаях клас- сы еще недостаточно деятельны. Нужно продолжить применять пере- мещение метода для передачи поведения в классы, пока протоколы не станут одинаковыми. Если для этого приходится осуществить избыточ- ное перемещение кода, можно попробовать компенсировать это Выде-
лением родительского класса (Extract Superclass ).
8.3. 17. Неполнота библиотечного класса
Повторное использование кода часто рекламируется как цель при- менения объектов. Значение этого аспекта переоценивается (достаточно простого использования). Не следует отрицать, однако, что программи- рование во многом основывается на применении библиотечных классов.
Разработчики библиотечных классов не всеведущи, и их не следует осуждать за это. Проблема в том, что часто считается дурным тоном и обычно оказывается невозможным модифицировать библиотечный класс, чтобы он выполнял какие-то желательные действия. Это означа- ет, что испытанная тактика вроде перемещения метода оказывается бес- полезной.
Для этой работы есть пара специализированных инструментов. Если в библиотечный класс надо включить всего лишь один-два новых мето- да, можно выбрать Введение внешнего метода (Introduce Foreign
Method). Если дополнительных функций достаточно много, необходимо применить Введение локального расширения (Introduce Local Exten-sion
).
375 8.3.18. Классы данных
Такие классы содержат поля, методы для получения и установки значений этих полей и ничего больше. Это – бессловесные хранилища данных, которыми другие классы наверняка манипулируют излишне обстоятельно. На ранних этапах в этих классах могут быть открытые поля, и тогда необходимо немедленно, пока никто их не обнаружил, применить Инкапсуляцию поля (Incapsulate Field). При наличии полей коллекций нужно проверить, инкапсулированы ли они должным обра- зом, и если нет, применить Инкапсуляцию коллекции (Incapsulate
Collection). Применить Удаление метода установки значения (Remove
Setting Method) ко всем полям, значение которых не должно изменяться.
Посмотреть, как эти методы доступа к полям используются другими классами. Далее попробовать с помощью перемещения метода перемес- тить методы доступа в класс данных. Если метод не удается перемес- тить целиком, обратиться выделению метода, чтобы создать такой ме- тод, который можно переместить. Через некоторое время можно начать применять сокрытие метода к методам получения и установки значений полей.
8.3.19. Отказ от наследования
Подклассам полагается наследовать методы и данные своих родите- лей. Но как быть, если наследование им не требуется? Обычная причина этого – неправильно задуманная иерархия. Необходимо создать новый класс на одном уровне с потомком и с помощью Спуска метода (Push
Down Method) Спуска поля (Push Down Field) вытолкнуть в него все бездействующие методы. Благодаря этому в родительском классе будет содержаться только то, что используется совместно.
Часто встречается совет делать все родительские классы абстракт- ными. М. Фаулер этого не советует, и замечает, по крайней мере, это годится не на все случаи жизни. С другой стороны, он считает нормаль- ным стилем работы создание подклассов для повторного использования части функций. Также отмечает, что если с не принятым наследством связаны какие-то проблемы, стоит следовать обычному совету. Однако не следует думать, что это надо делать всегда. Если подкласс повторно использует функции родительского класса, но не желает поддерживать его интерфейс, то нет возражений против отказа от реализаций, но от интерфейса отказываться не следует. В этом случае не надо возиться с иерархией; ее надо разрушить с помощью замены наследования делеги- рованием.
8.3.20. Комментарии
Просто удивительно, как часто встречается код с обильными ком- ментариями, которые появились в нем лишь потому, что код плохой. По мнению М. Фаулера, после рефакторинга комментарии часто оказыва-