提取参数对象有什么好处?

时间:2015-09-23 19:48:15

标签: intellij-idea coding-style refactoring automated-refactoring parameter-object

IntelliJ-IDEA中有一个重构工具,它允许我从方法中提取参数对象。

enter image description here

这将执行以下操作:

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 个答案:

答案 0 :(得分:0)

我认为您的第二个示例(namePerson)与您的第一个示例非常不同。在第一个示例中,您没有发送更多或更少的信息,而是以不同的形式发送完全相同的信息。在你的第二个例子中,正如你所指出的那样,你最终会发送很多信息,而这个功能并不需要履行它的职责。根据经验,您总是希望将发送函数的信息限制为执行任务所需的信息。在您的情况下,这是该人的名字。这有助于减少耦合并使您的函数不必了解太多其他类的行为/结构来执行它的任务。您可以假设您的名称排名功能有一天可能会用于使年龄组和其他信息加权排名,因此,发送整个Person对象会更好,但这不是现在的情况,所以我不会进入那个。

让我们回到你的第一个例子。正如我所提到的那样,在这种情况下,您最终会以另一种形式发送相同的信息。在哪种情况下,这比发送更长的基元列表更好?假设我们正在编码Red Square。在这个游戏中,你必须控制一个红色方块并躲避移动的蓝色矩形。我将向您介绍可以编写某些代码的两种不同方法。

第一个实现可能如下所示:

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, ...
}

以下是第二个实现,我们introduced a Parameter ObjectRectangle

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,或者像这样的错误)
  4. 我相信你的Point的第一个例子与Rectangle的例子比第二个例子(你最终注入更紧密的耦合你不需要的信息)更相似。如果您经常将一组参数发送到不同的函数,则可能表明它们作为一个实体发挥作用,并且可以从“引入参数对象”重构中受益。例如,在您的Point示例中,您很少(从不?)使用xy作为独立信息。您始终将它们用作一个实体,即(x,y)点。

    请记住,在使用自动重构工具时,您始终必须小心。 “Introduce Parameter Object”不应该盲目地用于每个函数调用。正如我之前所说,你应该问自己(概念上)将这些参数捆绑为一个单一实体是否有意义。话虽如此,重构可以帮助您的代码更具可读性,并减少发送函数所需的参数数量,从而减少错误的顺序或参数不匹配。不相信我?您是否注意到第一个collides(...)函数调用中的细微错误? : - )