Каковы преимущества извлечения объектов параметров?


В IntelliJ-IDEA есть инструмент рефакторинга, который позволяет мне извлекать объект параметра из метода.

Введите описание изображения здесь

Это будет делать что-то вроде следующего:

public interface ThirdPartyPoint {
    float getX();
    float getY();
}

До:

class Main {
    public static float distanceBetween(float x1, y1, x2, y2) {
        return distanceBetween(x1, y1), x2, y2);
    }

    public static float distanceBetween(ThirdPartyPoint point1, ThirdPartyPoint point2) {
        return distanceBetween(point1.getX(), point1.getY(), point2.getX(), point2.getY());
    }
}

После:

class Main {

    public static float distanceBetween(Point point1, Point point2) {
        return Math.sqrt(Math.pow(point2.getX() - point1.getX(), 2) + Math.pow(point2.getY() - point1.getY(), 2));
    }

    public static float distanceBetween(ThirdPartyPoint point1, ThirdPartyPoint point2) {
        return distanceBetween(new Point(point1.getX(), point2.getY()), new Point(point2.getX(), point2.getY()));
    }

    private static class Point {
        private final float x;
        private final float y;

        private Point(float x, float y) {
            this.x = x;
            this.y = y;
        }

        public float getX() {
            return x;
        }

        public float getY() {
            return y;
        }
    }
}

Почему это лучше, чем раньше?

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

У меня такое чувство, что сигнатуры методов обычно должны идти в противоположном направлении. Например, если у вас есть функция, которая выясняет, насколько популярно имя, например:

public int nameRanking(String name) {
    // do something with name
}

И вы извлекаете объект параметра следующим образом:

public int nameRanking(Person person) {
    // do something with person.getName()
}
Разве это не ухудшает ситуацию? Например, что делать, если после создания класса Person из меню рефакторинга я решу удалить метод getName(), потому что я не хочу, чтобы имя было открыто для всех людей, но другие классы использовали функцию nameRanking? Теперь мне нужно изменить мою функцию nameRanking. Если бы я использовал встроенный класс String, я знаю, что ничто, введенное в эту функцию, никогда бы не изменилось.
1 2

1 ответ:

Я считаю, что ваш второй пример (с name и Person) очень отличается от вашего первого примера. В первом примере вы не посылаете больше или меньше информации, вы посылаете точно такую же информацию в другой форме. Во втором примере, как вы заметили, вы в конечном итоге посылаете много информации, которая не нужна функции для выполнения ее обязанностей. Как правило, вы всегда хотите ограничить информацию, которую вы посылаете функции, тем, что ей нужно для выполнения своих задач. В вашем случае это имя человека. Это помогает уменьшить сцепление и избавляет ваши функции от необходимости знать слишком много о поведении/структуре других классов для выполнения своей задачи. Вы можете представить себе, что ваша функция ранжирования имен может когда-нибудь использоваться для ранжирования, взвешенного по возрастным группам и другой информации, и что по этой причине было бы лучше отправить целый объект Person, но сейчас это не так, поэтому я не буду вдаваться в это.

Давайте вернемся к ваш первый пример. Как я уже упоминал, в этом случае вы отправляете ту же информацию в другой форме. В каком случае это было бы лучше, чем посылать более длинный список примитивов? Допустим, мы кодируемКрасный Квадрат . В этой игре вам предстоит управлять красным квадратом вокруг и уворачиваться от движущихся синих прямоугольников. Я представлю вам два различных способа написания некоторых частей кода.

Первая реализация может выглядеть следующим образом:

int x2 = game.player.pos_x
int y2 = game.player.pos_y
int w2 = game.player.width
int h2 = game.player.height
for(Enemy enemy : game.enemies) {
    int x2 = enemy.pos_x
    int y2 = enemy.pos_y
    int w2 = enemy.width
    int h2 = enemy.height

    // If it is colliding with the player
    if(collides(x1, y1, w1, h1, x1, y2, w2, h2)) {
        die()
    }

    // If it is going out of bounds, bump it back inside
    if(isInBounds(0, 0, game.map.width, game.map.height, x2, y2, w2, h2)) {
        enemy.bump()
    }

    //... More functions that use x1, y1, w1, w1, ...
}

Здесь есть вторая реализация, где мы ввели объект параметра, Rectangle:

Rectangle playerRect = game.player.rect
for(Enemy enemy : game.enemies) {
    Rectangle enemyRect = enemy.rect

    // If it is colliding with the player
    if(collides(playerRect)) {
        die()
    }

    // If it is going out of bounds, bump it back inside
    if(isInBounds(game.map.rect, enemyRect)) {
        enemy.bump()
    }

    //... More functions that use playerRect and enemyRect
}

Некоторые преимущества второй реализации включают:

  1. более читабельные вызовы функций (меньше параметров)
  2. меньше места для ошибок при отправке параметров не по порядку (что, если я отправлю (width, height, x, y) вместо (x, y, width, height)?)
  3. вы уверены, что всегда будете работать с целыми экземплярами прямоугольников (нет возможности отправить функцию X вашего игрока вместо X противника или ошибки вот так)
Я считаю, что ваш первый пример с Point гораздо более похож на Rectangle, чем ваш второй пример (где вы в конечном итоге вводите более тесную связь с информацией, которая вам не нужна). Если у вас есть группа параметров, которые вы часто отправляете вместе в разные функции, это может быть признаком того, что они действуют как одна сущность, и будет полезно для рефакторинга "ввести объект параметров". Например, в вашем примере Point вы редко (никогда?) использовать x и y как самостоятельная информация. Вы всегда используете их как одну сущность, точку (x,y). Имейте в виду, что вы всегда должны быть осторожны при использовании инструментов автоматического рефакторинга. "Introduct Parameter Object " не следует использовать вслепую при каждом вызове функции. Как я уже говорил ранее, вы должны спросить себя, имеет ли смысл (концептуально) объединять эти параметры в одну единую сущность. С учетом вышесказанного, рефакторинг может помочь сделать ваш код более читабельным и уменьшить количество параметров, которые вы должны передать функции, в результате чего меньше ошибок в порядке или несоответствия параметров. Не веришь мне? Вы заметили тонкую ошибку в первом вызове функции collides(...)? :- )