Объектно-ориентированное злоупотребление кодом плохого вкуса

Java программист спецификация кода

: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 или не нуждаются в наследстве данных.



решать

  1. Создайте новое поле в подклассе для хранения суперкласса;
  2. Настройте функцию подкласса так, чтобы вместо этого она делегировала полномочия суперклассу;
  3. Затем удалите отношения наследства между двумя.



Извлечь суперкласс

проблема

Оба класса имеют схожие свойства.



решать

Создайте суперкласс для обоих классов и переместите те же свойства в суперкласс.



Расширенное чтение

использованная литература