12

编辑:我真的很感谢大家的意见。我从所有的回复中获得了一些东西,并且学到了很多关于 OOD 的知识。

我正在制作一个简单的虚拟桌面战争游戏。为了表示战场上的单位,我有以下简单的类层次结构:一个抽象类 Unit,以及两个派生类,Troop 和 Vehicle。

我有另一个类,其中包含游戏中所有单位的哈希表。哈希表值是 Unit 类型,所以我可以在 O(1) 时间内引用它们。

在大多数情况下,这很好,但有时调用者需要知道某物是部队还是车辆,才能从这些派生类中调用特定方法。为了适应这种情况,我创建了两个强制执行类型的 get 方法:

  public Troop getTroop(String uniqueID) {
    Unit potentialTroop = get(uniqueID);
    if(potentialTroop instanceof Vehicle) {
      throw new InternalError();
    }
    return (Troop) potentialTroop;
  }

  public Vehicle getVehicle(String uniqueID) {
    Unit potentialVehicle = get(uniqueID);
    if(potentialVehicle instanceof Troop) {
      throw new InternalError();
    }
    return (Vehicle) potentialVehicle;
  }

(注意 this 所属的类只是扩展了 Hashtable,所以这里使用的 get 方法是 Java 的 hashtable 的 get 方法。)

所以我认为这是糟糕的 OOD 设计,因为如果我进一步扩展单元,我将不得不向这个哈希表添加更多检查和更多#get 方法。

我这样说对吗?如果是这种情况,是否有人有其他 OOD 建议?

4

6 回答 6

3

在这种情况下,我不会扩展 HashTable(或任何其他类)。您的类可以在内部使用 HashTable,但通过扩展它,您会暴露很多公共 API。暴露的越少越好。您通常应该支持组合而不是继承。

这样您就可以更灵活地存储对象。在您的情况下,您可以在内部拥有 3 张地图;一个包含所有单位,一个仅用于部队,一个仅用于车辆。给定的单元将存储在两个地图中,因此您必须同步单元的添加和删除以确保各个地图之间的完整性。

于 2013-02-17T00:17:40.800 回答
3

为什么不只实现一个get返回的方法Unit呢?我相信如果这两个类有非常具体的不同方法,那么它们就没有很多共同点,不是吗?您希望它们成为子类的事实Unit使我认为它们的相似性超出了“它们是在地图上具有位置的对象”这一事实。在这种情况下,“错误”的部分将给方法提供不同的名称并分别调用它们。一种让事情变得更好的方法是

  • 从哈希表中只返回 Unit
  • 正如已经建议的那样,在可能的情况下制作综合方法,例如behave(), move(),这对两个类都是通用的,因此可以在不强制转换的情况下调用(即使它们会做不同的事情)。使用您创建的层次结构!这些方法最终将调用您之前创建的小型模块化方法(现在将变为private)。
  • 您并不总是需要知道 的类型Unit,对吗?仅在必要时(无法使用上述技术解决),将此类工作委托给您定义为“调用者”的对象,我不认为它是一个单一的、固定的实体。换句话说,只有当你必须决定你的单位是否用步枪射击时才进行类型检查。当您要执行常见任务时,无需预期这种区别。
于 2013-02-17T00:48:18.260 回答
3

这是一个简单的方法,不一定是最好的,但它满足这些要求:

  1. 能够从您的Unit集合中动态获取特定类型
  2. 以后可以添加其他Unit类型,而无需添加一堆处理程序方法。

该解决方案使用“模板”类来执行匹配:

@SuppressWarnings("unchecked")
public <T extends Unit> T getSpecializedUnitType(Class<T> unitTypeClass, String uniqueID) {
    Unit potentialTroop = units.get(uniqueID);
    if(potentialTroop == null) return null;

    return potentialTroop.getClass().equals(unitTypeClass) ?
        (T) potentialTroop : null;
}

我假设您将更正您的代码,而不是从 扩展Map,而是封装它。

于 2013-02-17T00:49:30.890 回答
1

这个 getVehicle() 和 getTroops() 几乎就像一个 if(elem instanceof X) 语句链,除了它有例外。我讨厌那种链,但在这种情况下,我更喜欢这种链,并且不理会哈希表。

但是,如果您需要调用的特定方法可以被认为是 Vehicle 或 Troop 的“特定行为”,那么您应该考虑将它们放在 Unit 类中的抽象方法中,并在每个类中覆盖它。如果不是这种情况,请提供有关您需要对您的单元做什么的更多信息,以便我们可以找到一个很好的概括。

于 2013-02-17T00:20:30.103 回答
1

我喜欢 Perception 提供的解决方案,但我认为它可以进一步改进。这是我的版本:

public <T extends Unit> T getSpecializedUnitType(Class<T> unitClass, String id) {
    Unit unit = units.get(id);
    return unitClass.cast(unit);
}

unit如果是null,则不返回任何抑制的警告,null如果不是,则将其强制转换为必要的类型。如果类型错误,ClassCastException则抛出。

于 2013-02-17T07:34:20.277 回答
1

这是类设计中很常见的问题,不幸的是,没有一个好的解决方案,因为解决方案会随着问题的细节而变化。比您现在拥有的更好的设计是通过一个通用接口(例如您的 Unit)来表达每种类型的类型信息和功能。

这是一种我认为更好的可能性,尽管有些人会认为它仍然不是很好,因为抛出 UnsupportedOperationException 的方法。我使用 Groovy 更简洁,但它与 Java 足够接近,您应该明白这一点。看看它是否满足您的需求:

abstract class Unit {
    enum UnitType { TROOP, VEHICLE }
    abstract UnitType getType()
    TroopAbilities getTroopAbilities() {
        throw new UnsupportedOperationException('not a Troop') 
    }
    VehicleAbilities getVehicleAbilities() { 
        throw new UnsupportedOperationException('not a Vehicle') 
    }
}

interface TroopAbilities {
    void doTroopThing()
}

interface VehicleAbilities {
    void doVehicleThing()
}

class Troop extends Unit implements TroopAbilities {
    void doTroopThing() { println 'something troopy' }
    UnitType getType() { UnitType.TROOP }
    TroopAbilities getTroopAbilities() { this }
}

class Vehicle extends Unit implements VehicleAbilities {
    void doVehicleThing() { println 'something vehicle-ish' }
    UnitType getType() { UnitType.VEHICLE }
    VehicleAbilities getVehicleAbilities() { this }
}

List<Unit> units = [new Troop(), new Vehicle(), new Troop()]
for (Unit unit : units) {
    switch (unit.getType()) {
        case Unit.UnitType.TROOP:
            unit.getTroopAbilities().doTroopThing()
            break;
        case Unit.UnitType.VEHICLE:
            unit.getVehicleAbilities().doVehicleThing()
            break;
        default:
            throw new IllegalStateException(
                "New unit type that's not accounted for: " + unit.getType())
    }
}

此外,如果你在接口和实现之间做出明确的突破,你的生活会更简单,所以 Unit 应该更像:

interface Unit {
    enum UnitType { TROOP, VEHICLE }
    UnitType getType()
    TroopAbilities getTroopAbilities()
    VehicleAbilities getVehicleAbilities()
}

abstract class AbstractUnit implements Unit {
    TroopAbilities getTroopAbilities() {
        throw new UnsupportedOperationException('not a Troop')
    }
    VehicleAbilities getVehicleAbilities() {
        throw new UnsupportedOperationException('not a Vehicle')
    }
}

然后,您的具体单元类型将扩展 AbstractUnit,然后您正确地使用继承来实现代码重用和多态性,以允许每个子类以自己的方式对消息做出反应。唯一的灰色区域是 get*Abilties() 方法,但目前我想不出解决这些问题的好方法。

更新以减少工作量:如果您想将其缩减到最低限度并删除一些可扩展性选项和枚举的安全性,您可以这样做:

interface Unit {
    abstract String getType()
    Troop asTroop()
    Vehicle asVehicle()
}

abstract class AbstractUnit implements Unit {
    Troop asTroop() {
        throw new UnsupportedOperationException('not a Troop')
    }
    Vehicle asVehicle() {
        throw new UnsupportedOperationException('not a Vehicle')
    }
}

class Troop extends AbstractUnit {
    void doTroopThing() { println 'something troopy' }
    String getType() { "troop" }
    Troop asTroop() { this }
}

class Vehicle extends AbstractUnit {
    void doVehicleThing() { println 'something vehicle-ish' }
    String getType() { "vehicle" }
    Vehicle asVehicle() { this }
}

List<Unit> units = [new Troop(), new Vehicle(), new Troop()]
for (Unit unit : units) {
    switch (unit.getType()) {
        case "troop":
            unit.asTroop().doTroopThing()
            break;
        case "vehicle":
            unit.asVehicle().doVehicleThing()
            break;
        default:
            throw new IllegalStateException(
                "New unit type that's not accounted for: " + unit.getType())
    }
}
于 2013-02-17T00:37:12.203 回答