首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >干法攻防

干法攻防
EN

Code Review用户
提问于 2014-07-16 10:26:16
回答 2查看 2.2K关注 0票数 14

我在一个类上有这两个方法,它们只在一个方法调用中不同。显然,这是非常不干燥的,特别是因为两者使用相同的公式。有人能给我关于如何收拾这件事的建议吗?

代码语言:javascript
复制
public class PlayerCharacter {
    int level;
    public int getAttack() {
        int attack = 1 + level;
        for(Equipment e : equipments)
           attack += e.getAttack();
        return attack * Math.sqrt(level);
    }
    public int getDefense() {
        int attack = 1 + level;
        for(Equipment e : equipments)
           attack += e.getDefense();
        return attack * Math.sqrt(level);
    }
}

我考虑将循环重构为外部方法,但不同的方法调用使我感到困惑。

EN

回答 2

Code Review用户

发布于 2014-07-17 11:11:21

老实说,我不确定我是否会费心把这些方法合并。是的,它们本质上是相同的,但是仔细看一下,重复的代码仍然只有几行,其中大部分是你所使用的语言和类设计中不可避免的样板代码。

通常,当考虑是否将一些(看似)重复的代码干涸时,我建议先问自己几个问题:

  1. 这是真的重复,还是只是偶然的相似?也就是说,如果您要更改其中一个方法,您是否也需要更改另一个方法?对于您的例子来说,这似乎是一个特别相关的问题,因为大多数明显的重复来自于攻击和防御公式是如此相似的事实。如果它们在设计上是相似的,因此更改一个总是需要更改另一个,那么这就是真正的复制,可能需要重构。另一方面,如果你能预见到将来你可能会想要使这些公式有所不同,那么现在把它们结合起来实际上可能会对以后的结果是有害的。
  2. 合并这些方法会使代码更短、更简单吗?如果消除重复会使代码更长或更难理解,那么您并没有真正获得任何东西,可能只会使事情变得更糟。所有这些“经验法则”的真正目的是节省您的时间,并保持代码的简单、高效和可维护性。如果你发现自己浪费了大量的开发时间,或者仅仅为了遵循一条经验法则而引入了很多额外的复杂性,那么你可能把这个规则做得太过分了。
  3. 还会有更多这样的重复吗?这确实是前一个问题的必然结果。如果你知道只有两种像这样的方法,而且它们不太可能变得比现在更复杂,那么去复制它们(至少现在)可能是一种浪费。但是,如果您认为您可能需要第三份或第四份副本,那么可能是时候考虑将它们合并成一个方法了。有时,如果您期望一段代码在将来获得更多的用途,那么即使还没有复制,也可以使代码具有足够的灵活性以允许这样的使用而不重复。不过,不要在这件事上走得太远,也要时刻记住YAGNI。

尽管如此,如果我真的相信这两种方法之间的相似性是基本的,而不仅仅是偶然的,而且如果我真的想消除冗余,那么我要做的(至少首先)是将共享的(1 + level + sum(equipment)) * sqrt(level)公式分解成一个单独的(静态)方法,留给我:

代码语言:javascript
复制
public class PlayerCharacter {
    private int level;

    public int getAttack() {
        int attackBonus = 0;
        for (Equipment e : equipments) attackBonus += e.getAttack();
        return powerFormula(level, attackBonus);
    }
    public int getDefense() {
        int defenseBonus = 0;
        for (Equipment e : equipments) defenseBonus += e.getDefense();
        return powerFormula(level, defenseBonus);
    }

    private static int powerFormula(int level, int bonus) {
        return (1 + level + bonus) * (int) Math.sqrt(level);
    }
}

(使powerFormula()方法静态的选择主要是一种文体选择,但我认为应该强调的是,它只是封装了一个简单的数学公式,该公式独立于任何特定的字符对象。)

现在,仍然有相当多的明显重复,但这实际上只是总结球员的装备的攻击/防御奖金的样板。在Java8中,我们可以通过使用方法参考来清理剩余的复制,例如:

代码语言:javascript
复制
public class PlayerCharacter {
    private int level;

    public int getAttack() {
        int attackBonus = getEquipmentBonus(Equipment::getAttack);
        return powerFormula(level, attackBonus);
    }
    public int getDefense() {
        int defenseBonus = getEquipmentBonus(Equipment::getDefense);
        return powerFormula(level, defenseBonus);
    }

    private int getEquipmentBonus(ToIntFunction<Equipment> func) {
        return equipments.stream().mapToInt(func).sum();
    }
    private static int powerFormula(int level, int bonus) {
        return (1 + level + bonus) * (int) Math.sqrt(level);
    }
}

(我希望这段代码是正确的,因为我实际上没有Java 8编译器来测试它。通过将getEquipmentBonus()powerFormula()方法合并成一个方法,它可以进一步缩短,但是保持它们的分离对我来说更清晰--这样,每种方法都有一个明确的职责。对于getEquipmentBonus()方法,我选择使用流接口,但您也可以使用一个简单的循环。)

对于早期的Java版本,重复循环似乎是不可避免的,除非您想使用内部类来模拟Java 8 lambda表达式或类似的东西。

在任何情况下,上述两个重构版本都实现了这样的目标:如果您想要更改玩家攻击/防御能力公式,则只能在一个地方更改该公式,并且更改将影响两种计算。如果这不是您想要的,您可能会更好地保留原来的代码。

票数 12
EN

Code Review用户

发布于 2014-07-16 22:51:37

检查这些相似之处,我建议您将攻击/防御属性分解为一个单独的类。然后,可以将其用作参数。enum是该属性的优秀候选--我将称之为Stance,我相信您会想出一个更好的名称。

结果非常简洁,我认为非常简洁:

代码语言:javascript
复制
enum Stance {

    Attack {
                @Override
                int getStrength(Equipment equipmemt) {
                    // When on attack - deliver the attackng strength of the equipment.
                    return equipmemt.getAttack();
                }

            },
    Defend {
                @Override
                int getStrength(Equipment equipmemt) {
                    // When in defence - deliver the defensive strength of the equipment.
                    return equipmemt.getDefence();
                }

            };

    // All stances must delver a value depending on the equipment.
    abstract int getStrength(Equipment equipmemt);

    // Convenience method for collections of Equipment.
    int getStrength(Iterable<Equipment> es) {
        int total = 0;
        for (Equipment e : es) {
            total += getStrength(e);
        }
        return total;
    }
}

public class PlayerCharacter {

    int level;

    public double getStrength(Stance stance) {
        // Sum all strengths of all equipment in that stance.
        int strength = 1 + level + stance.getStrength(equipments);
        // Work out the value.
        return strength + Math.sqrt(level);
    }

    public double getAttack() {
        // It's the attacking strength.
        return getStrength(Stance.Attack);
    }

    public double getDefense() {
        // It's the defending strength.
        return getStrength(Stance.Defend);
    }
}
票数 6
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/57177

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档