12

編集:私は本当にみんなの入力に感謝します。私はすべての回答から何かを得て、OODについて多くを学びました。

シンプルな仮想卓上ウォーゲームを作っています。戦場でユニットを表すために、次の単純なクラス階層があります。抽象クラスUnitと、2つの派生クラス、TroopとVehicleです。

ゲーム内のすべてのユニットのハッシュテーブルを持つ別のクラスがあります。ハッシュテーブルの値はユニットタイプなので、O(1)時間で参照できます。

ほとんどの場合、これは問題ありませんが、呼び出し元は、派生クラスから特定のメソッドを呼び出すために、何かが軍隊なのか乗り物なのかを知る必要がある場合があります。これに対応するために、タイプを強制する2つの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;
  }

(これが属するクラスは単にHashtableを拡張するだけなので、ここで使用されているgetメソッドはJavaのハッシュテーブルのgetメソッドであることに注意してください。)

したがって、これはOODの設計としては不十分だと思います。これは、ユニットをさらに拡張すると、このハッシュテーブルにチェックと#getメソッドを追加する必要があるためです。

私はこれを言うのは正しいですか?これが事実である場合、誰かが代替のOOD提案を持っていますか?

4

6 に答える 6

3

この場合、HashTable(または他のクラス)を拡張しません。クラスは内部でHashTableを使用できますが、それを拡張することで、多くのパブリックAPIを公開します。露出が少ないほど良いです。一般的に、継承よりも構成を優先する必要があります。

そうすれば、オブジェクトの保存方法をより柔軟に設定できます。あなたの場合、内部に3つのマップを持つことができます。1つはすべてのユニットを含み、1つは軍隊専用、もう1つは車両専用です。特定のユニットは2つのマップに保存されるため、ユニットの追加と削除を同期して、さまざまなマップ間の整合性を確保する必要があります。

于 2013-02-17T00:17:40.800 に答える
3

getを返すメソッドを1つだけ実装してみませんUnitか?2つのクラスに非常に具体的で異なるメソッドがある場合、それらには多くの共通点がないと思いますか?それらをサブクラスにしたいという事実Unitは、それらの類似性が「それらはマップ上の位置を持つオブジェクトである」という事実を超えていると私に思わせます。この場合、「間違った」部分は、メソッドに異なる名前を付け、それらを別々に呼び出すことです。物事をより良くする方法は

  • ハッシュテーブルからユニットのみを返す
  • すでに提案したようbehave()に、可能な場合は、のような包括的なメソッドを作成しmove()ます。これは、両方のクラスに共通である可能性があるため、キャストせずに呼び出すことができます(異なることを行う場合でも)。作成した階層を使用してください!これらのメソッドは、最終的に、以前に作成した小さなモジュラーメソッドを呼び出します(これは現在)になりprivateます。
  • いつもタイプを知る必要はありUnitませんよね?必要な場合(上記の手法では解決できない)にのみ、そのような作業を「発信者」として定義したものに委任します。これは、単一の固定されたエンティティであるとは思われません。つまり、ユニットがライフルで撃つかどうかを判断する必要がある場合にのみ、タイプのチェックを実行します。一般的なタスクを実行する場合、そのような区別を予期する必要はありません。
于 2013-02-17T00:48:18.260 に答える
3

これを行う簡単な方法は次のとおりですが、必ずしも最善ではありませんが、次の要件を満たしています。

  1. Unitコレクションから特殊なタイプを動的に取得できる
  2. Unitハンドラーメソッドを追加しなくても、後でタイプを追加できます。

このソリューションでは、「template」クラスを使用してマッチングを実行します。

@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

これはクラス設計でよくある問題ですが、問題の詳細によって解決策が異なるため、残念ながら、単一の適切な解決策はありません。現在の設計よりも優れた設計は、各型の型情報と機能が、ユニットなどの共通のインターフェースを介して表現される設計です。

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())
    }
}

また、インターフェースと実装の間にすっきりとした休憩をとれば、あなたの生活はよりシンプルになるので、ユニットは実際には次のようになります。

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 に答える
1

Perceptionが提供したソリューションは気に入っていますが、もっと改善できると思います。これが私のバージョンです:

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

抑制された警告は返されません。そうでない場合unitnullnull必要なタイプにキャストされます。タイプが間違っている場合は、ClassCastExceptionがスローされます。

于 2013-02-17T07:34:20.277 に答える