:notebook: Эта статья была заархивирована в: "blog"
Переведено с: https://sourcemaking.com/refactoring/smells/oo-abusers
Неправильное использование группы неприятных запахов Object-Orientation Abuses означает, что код частично или полностью нарушает принципы объектно-ориентированного программирования.
Объявление переключателя
Операторы Switch
У вас есть комплекс
switch
заявление илиif
заявление о последовательности.
проблема вызывает
Одной из наиболее очевидных характеристик объектно-ориентированных программ является то, что они используют меньшеswitch
иcase
утверждение. По сути,switch
Проблема с утверждением заключается в повторении (if
последовательность тоже). вы будете часто находитьswitch
Заявления разбросаны по разным местам. Если вы хотите добавить к нему новыйcase
пункта, необходимо найти всеswitch
Утверждения и изменить их. Элегантным решением этой проблемы может стать объектно-ориентированный полиморфизм.
Чаще всего, когда вы видитеswitch
утверждение, вам следует подумать о замене его полиморфизмом.
Решение
- Вопрос в том, в каком полиморфизме они проявляются? Оператор switch часто выбирается в соответствии с типом кода, который вы хотите «связать с этим типом функции или кода класса», поэтому мы должны использовать
提炼函数(Extract Method)
будетswitch
Оператор перегоняется в отдельную функцию, за которой следует搬移函数(Move Method)
Переместите его в класс, который нуждается в полиморфизме. - если ваш
switch
основан на коде типа для идентификации филиала, вы можете использовать以子类取代类型码(Replace Type Code with Subclass)
или以状态/策略模式取代类型码(Replace Type Code with State/Strategy)
. - Как только у вас есть такая структура наследования, вы можете использовать
以多态取代条件表达式(Replace Conditional with Polymorphism)
. - Полиморфизм не нужен, если условных ветвей не много и они вызывают одну и ту же функцию с разными аргументами. В этом случае вы можете использовать
以明确函数取代参数(Replace Parameter with Explicit Methods)
. - Если один из ваших критериев выбора равен нулю, вы можете использовать
引入 Null 对象(Introduce Null Object)
.
доход
- Улучшить организацию кода.
когда игнорировать
- если
switch
Действия просто выполняют простые действия, и нет необходимости в рефакторинге. -
switch
Часто упоминается семейством шаблонов проектирования Factory (工厂方法模式(Factory Method)
и抽象工厂模式(Abstract Factory)
) используется, и в этом случае не требуется рефакторинг.
Описание метода рефакторинга
Метод извлечения
проблема
У вас есть фрагмент кода, который можно сгруппировать.
void printOwing() {
printBanner();
//print details
System.out.println("name: " + name);
System.out.println("amount: " + getOutstanding());
}
решать
Переместите этот код в новую функцию, используйте вызов для замены старого кода.
void printOwing() {
printBanner();
printDetails(getOutstanding());
}
void printDetails(double outstanding) {
System.out.println("name: " + name);
System.out.println("amount: " + outstanding);
}
Метод перемещения
проблема
В вашей программе есть функция, которая больше взаимодействует с другим классом, чем с тем, в котором она находится: вызывается последний или вызывается последним.
решать
Создайте новую функцию с аналогичным поведением в классе, на который функция чаще всего ссылается. Превратите старую функцию в чистую функцию делегата или полностью удалите старую функцию.
Заменить код типа подклассом
проблема
У вас есть код непеременного типа, который влияет на поведение класса.
решать
Замените этот код типа подклассом.
Замените код типа на состояние/стратегию
проблема
У вас есть код типа, который влияет на поведение класса, но вы не можете избавиться от него с помощью наследования.
решать
Замените код типа на объект состояния.
Заменить условное выражение полиморфизмом
проблема
У вас получилось условное выражение, оно заключается в выборе разного поведения в зависимости от типа объекта.
class Bird {
//...
double getSpeed() {
switch (type) {
case EUROPEAN:
return getBaseSpeed();
case AFRICAN:
return getBaseSpeed() - getLoadFactor() * numberOfCoconuts;
case NORWEGIAN_BLUE:
return (isNailed) ? 0 : getBaseSpeed(voltage);
}
throw new RuntimeException("Should be unreachable");
}
}
решать
Поместите каждую ветвь этого условного выражения в переопределенную функцию внутри подкласса, а затем объявите исходную функцию абстрактной.
abstract class Bird {
//...
abstract double getSpeed();
}
class European extends Bird {
double getSpeed() {
return getBaseSpeed();
}
}
class African extends Bird {
double getSpeed() {
return getBaseSpeed() - getLoadFactor() * numberOfCoconuts;
}
}
class NorwegianBlue extends Bird {
double getSpeed() {
return (isNailed) ? 0 : getBaseSpeed(voltage);
}
}
// Somewhere in client code
speed = bird.getSpeed();
Заменить параметр явными методами
проблема
У вас есть функция, которая ведет себя по-разному в зависимости от значений параметров.
void setValue(String name, int value) {
if (name.equals("height")) {
height = value;
return;
}
if (name.equals("width")) {
width = value;
return;
}
Assert.shouldNeverReachHere();
}
решать
Для каждого возможного значения этого параметра создайте отдельную функцию.
void setHeight(int arg) {
height = arg;
}
void setWidth(int arg) {
width = arg;
}
Ввести нулевой объект
проблема
Вам нужно дважды проверить, является ли объект нулевым.
if (customer == null) {
plan = BillingPlan.basic();
}
else {
plan = customer.getPlan();
}
решать
Замените нулевые значения с нулевыми объектами.
class NullCustomer extends Customer {
Plan getPlan() {
return new NullPlan();
}
// Some other NULL functionality.
}
// Replace null values with Null-object.
customer = (order.customer != null) ? order.customer : new NullCustomer();
// Use Null-object as if it's normal subclass.
plan = customer.getPlan();
временное поле
Значения временных полей имеют смысл только в определенном контексте, вне этого контекста они ничто.
проблема вызывает
Иногда вы увидите объекты, в которых переменная экземпляра устанавливается только для определенной ситуации. Подобный код трудно понять, потому что вы обычно считаете, что объект нуждается в всех его переменных во все времена. Угадая, какая переменная установлена, когда она не используется, будет сводить вас с ума. Как правило, временные поля создаются, когда алгоритм требует много ввода. Следовательно, чтобы избежать функций со слишком многими параметрами, программист решил создать временные поля для этих данных в классе. Эти временные поля используются только в алгоритмах и являются бесполезными в противном случае. Этот код не легко понять. Вы ожидаете увидеть данные для полей объектов, но по какой-то причине они всегда пусты.
Решение
- в состоянии пройти
提炼类(Extract Class)
Повторите все коды временного поля и его операции в отдельный класс. Кроме того, вы можете использовать以函数对象取代函数(Replace Method with Method Object)
Для достижения той же цели. -
引入 Null 对象(Introduce Null Object)
Создайте нулевый объект в случае «переменных», чтобы избежать написания условных выражений.
доход
- Лучшая ясность и организация кода.
Описание метода рефакторинга
Извлечь класс
проблема
Класс делает больше, чем одну вещь.
решать
Создайте новый класс и переместите соответствующие поля и функции из старого класса в новый класс.
Заменить метод объектом метода
проблема
У вас есть функция, которая настолько длинная, что ее локальные переменные настолько переплетены, что вы не можете применить метод извлечения.
class Order {
//...
public double price() {
double primaryBasePrice;
double secondaryBasePrice;
double tertiaryBasePrice;
// long computation.
//...
}
}
решать
Вынести функцию в отдельный класс, сделав локальные переменные полями этого класса. Затем вы можете разделить функцию на несколько функций в этом классе.
class Order {
//...
public double price() {
return new PriceCalculator(this).compute();
}
}
class PriceCalculator {
private double primaryBasePrice;
private double secondaryBasePrice;
private double tertiaryBasePrice;
public PriceCalculator(Order order) {
// copy relevant information from order object.
//...
}
public double compute() {
// long computation.
//...
}
}
Ввести нулевой объект
проблема
Вам нужно дважды проверить, является ли объект нулевым.
if (customer == null) {
plan = BillingPlan.basic();
}
else {
plan = customer.getPlan();
}
решать
Замените нулевые значения нулевыми объектами.
class NullCustomer extends Customer {
Plan getPlan() {
return new NullPlan();
}
// Some other NULL functionality.
}
// Replace null values with Null-object.
customer = (order.customer != null) ? order.customer : new NullCustomer();
// Use Null-object as if it's normal subclass.
plan = customer.getPlan();
Похожие классы
Альтернативные классы с разными интерфейсами
Два класса с разными функциями, но делающие одно и то же.
проблема вызывает
Это часто происходит потому, что программист, создавший класс, не знает, что класс, реализующий эту функциональность, уже существует.
Решение
- Если две функции делают одно и то же, но имеют разные сигнатуры, используйте
函数改名(Rename Method)
Переименованы в соответствии с их назначением. - использовать
搬移函数(Move Method)
,添加参数(Add Parameter)
и令函数携带参数(Parameterize Method)
чтобы имя метода и реализация соответствовали друг другу. - Если дублируется только часть функциональности двух классов, попробуйте использовать
提炼超类(Extract Superclass)
. В этом случае существующий класс стал суперклассом. - Когда вы, наконец, выберете и примените метод рефакторинга, возможно, вы сможете удалить один из классов.
доход
- Устраните ненужное дублирование кода и сократите его.
- Код стал более читабельным (больше не нужно гадать, почему есть два класса с одной и той же функцией).
когда игнорировать
- Объединенный класс иногда невозможен, или так сложно, что он не имеет смысла. Например: две подобные функции существуют в разных классах в библиотеке lib.
Описание метода рефакторинга
Метод переименования
проблема
Имя функции неправильно не раскрывает цель функции.
class Person {
public String getsnm();
}
решать
Измените имя функции.
class Person {
public String getSecondName();
}
Метод перемещения
проблема
В вашей программе есть функция, которая больше взаимодействует с другим классом, чем с тем, в котором она находится: вызывается последний или вызывается последним.
решать
Создайте новую функцию с аналогичным поведением в классе, на который функция чаще всего ссылается. Превратите старую функцию в чистую функцию делегата или полностью удалите старую функцию.
Добавить параметр
проблемаФункция нуждается в дополнительной информации от вызывающей стороны.
class Customer {
public Contact getContact();
}
решатьДобавьте объектную функцию к этой функции и позвольте объекту передавать информацию, необходимую функции.
class Customer {
public Contact getContact(Date date);
}
Сделать функцию переносом параметров (метод параметризации)
проблема
Несколько функций были похожи, но в теле функции содержатся разные значения.
решать
Создайте одну функцию, которая принимает параметры для выражения различных значений.
Извлечь суперкласс
проблема
Оба класса имеют схожие свойства.
решать
Создайте суперкласс для обоих классов и переместите те же свойства в суперкласс.
отвергнутый подарок
Отказано в завещании
Подклассы используют только некоторые методы и свойства суперкласса. Другие подарки от родительского класса становятся обузой.
проблема вызывает
Некоторые люди создают подклассы просто для повторного использования некоторого кода из суперкласса. Но на самом деле суперклассы и подклассы совершенно разные.
Решение
- Если наследование не имеет смысла и между подклассом и суперклассом действительно нет ничего общего, вы можете использовать
以委托取代继承(Replace Inheritance with Delegation)
Ликвидировать наследство. - Если уместно наследование, удалите ненужные поля и методы в подклассах. использовать
提炼超类(Extract Superclass)
Извлеките из суперкласса все поля и функции, полезные для подклассов, поместите их в новый суперкласс и позвольте обоим классам наследовать его.
доход
- Улучшите ясность и организацию кода.
Описание метода рефакторинга
Замените наследование делегированием
проблема
Подкласс использует только часть интерфейса Super-Class или не нуждаются в наследстве данных.
решать
- Создайте новое поле в подклассе для хранения суперкласса;
- Настройте функцию подкласса так, чтобы вместо этого она делегировала полномочия суперклассу;
- Затем удалите отношения наследства между двумя.
Извлечь суперкласс
проблема
Оба класса имеют схожие свойства.
решать
Создайте суперкласс для обоих классов и переместите те же свойства в суперкласс.
Расширенное чтение
- Плохой запах кода и рефакторинг
- Раздувание кода с неприятным запахом кода
- Злоупотребление объектно-ориентированным кодом
- Барьеры на пути к изменениям: плохой запах кода
- Ненужный запах плохого кода
- Сочетание запахов плохого кода
использованная литература
- Рефакторинг — улучшение дизайна существующего кода - by Martin Fowler
- https://sourcemaking.com/refactoring