2

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 类,我知道注入这个函数的任何东西都不会改变。

4

1 回答 1

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

这是第二个实现,我们在其中引入了 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 的函数,或者类似的错误)

我相信你的第一个例子比你的第二个例子Point更相似Rectangle(你最终注入了更紧密的耦合到你不需要的信息)。如果您有一组经常一起发送到不同函数的参数,则可能表明它们充当一个实体,并且将从“引入参数对象”重构中受益。例如,在您的Point示例中,您很少(从不?)使用xy作为独立信息。您总是将它们用作一个实体,一个点(x,y)

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

于 2015-09-27T19:24:03.210 回答