簡體   English   中英

關於具有許多不同子類的類型切片的設計問題

[英]Design problem regarding type slicing with many different subclasses

我經常遇到的一個基本問題,但是找到了一個干凈的解決方案,是你想要為公共基類或接口的不同對象之間的交互編寫行為的問題。 為了使它有點具體,我將舉一個例子;

Bob一直在編寫支持“酷地理效應”的策略游戲。 這些簡單的約束,例如如果部隊在水中行走,他們減速25%。 如果他們在草地上行走,他們減速5%,如果他們在人行道上行走,他們減速0%。

現在,管理層告訴鮑勃,他們需要新的部隊。 會有吉普車,船只和氣墊船。 此外,他們希望吉普車在開車進入水中時會受到傷害,而氣墊船則會忽略所有三種地形類型。 有傳言說,他們可能會添加另一種地形類型,其功能甚至比減速單位和受到傷害還要多。

下面是一個非常粗略的偽代碼示例:

public interface ITerrain
{
    void AffectUnit(IUnit unit);
}

public class Water : ITerrain
{
    public void AffectUnit(IUnit unit)
    {
        if (unit is HoverCraft)
        {
            // Don't affect it anyhow
        }
        if (unit is FootSoldier)
        {
            unit.SpeedMultiplier = 0.75f;
        }
        if (unit is Jeep)
        {
            unit.SpeedMultiplier = 0.70f;
            unit.Health -= 5.0f;
        }
        if (unit is Boat)
        {
            // Don't affect it anyhow
        }
        /*
         * List grows larger each day...
         */
    }
}
public class Grass : ITerrain
{
    public void AffectUnit(IUnit unit)
    {
        if (unit is HoverCraft)
        {
            // Don't affect it anyhow
        }
        if (unit is FootSoldier)
        {
            unit.SpeedMultiplier = 0.95f;
        }
        if (unit is Jeep)
        {
            unit.SpeedMultiplier = 0.85f;
        }
        if (unit is Boat)
        {
            unit.SpeedMultiplier = 0.0f;
            unit.Health = 0.0f;
            Boat boat = unit as Boat;
            boat.DamagePropeller();
            // Perhaps throw in an explosion aswell?
        }
        /*
         * List grows larger each day...
         */
    }
}

正如您所看到的,如果Bob從一開始就擁有可靠的設計文檔,事情會更好。 隨着單元和地形類型的數量增加,代碼復雜性也會增加。 Bob不僅要擔心找出哪些成員可能需要添加到單元界面,而且他還必須重復很多代碼。 新地形類型很可能需要從基本IUnit接口獲得的信息中的附加信息。

每次我們在游戲中添加另一個單元時,必須更新每個地形以處理新單元。 顯然,這會產生大量的重復,更不用說確定正在處理的單元類型的丑陋的運行時檢查。 我在這個例子中選擇了對特定子類型的調用,但是這些調用是必要的。 一個例子是當船撞到陸地時,它的螺旋槳應該被損壞。 並非所有單位都有螺旋槳。

我不確定這個問題叫什么,但它是一個多對多的依賴,我很難解耦。 我不希望ITerrain上的每個IUnit子類都有100個重載,因為我希望通過耦合來清理它。

關於這個問題的任何亮點都是備受追捧的。 也許我正在考慮一起走出軌道?

地形具有地形屬性

地形屬性是多維的。

單位有一個推進力。

推進與地形屬性兼容。

單位通過地形訪問以推進作為參數。 這被委托給了推進器。

作為訪問的一部分,單位可能會受到地形的影響。

單位代碼對推進一無所知。 除了地形屬性和推進之外,地形類型可以改變而不改變任何東西。 Propuslion的構造者保護現有單元免受新的旅行方式的影響。

您遇到的限制是C#與其他一些OOP語言不同,缺少多個調度

換句話說,給定這些基類:

public class Base
{
    public virtual void Go() { Console.WriteLine("in Base"); }
}

public class Derived : Base
{
    public virtual void Go() { Console.WriteLine("in Derived"); }
}

這個功能:

public void Test()
{
    Base obj = new Derived();
    obj.Go();
}

即使引用“obj”是Base類型,也會正確輸出“在Derived中”。 這是因為在運行時 C#將正確地找到要派生的派生最多的Go()。

但是,由於C#是單一的調度語言,它只對“第一個參數”執行此操作,該參數在OOP語言中隱式為“this”。 以下代碼不像上面那樣工作:

public class TestClass
{
    public void Go(Base b)
    {
        Console.WriteLine("Base arg");
    }

    public void Go(Derived d)
    {
        Console.WriteLine("Derived arg");
    }

    public void Test()
    {
        Base obj = new Derived();
        Go(obj);
    }
}

這將輸出“Base arg”,因為除了“this”之外,所有其他參數都是靜態調度的,這意味着它們在編譯時綁定到被調用的方法。 在編譯時,編譯器唯一知道的是傳遞的參數的聲明類型(“Base obj”)而不是它的實際類型,因此方法調用綁定到Go(Base b)。

那么你的問題的解決方案是基本上手工創作一個小方法調度程序:

public class Dispatcher
{
    public void Dispatch(IUnit unit, ITerrain terrain)
    {
        Type unitType = unit.GetType();
        Type terrainType = terrain.GetType();

        // go through the list and find the action that corresponds to the
        // most-derived IUnit and ITerrain types that are in the ancestor
        // chain for unitType and terrainType.
        Action<IUnit, ITerrain> action = /* left as exercise for reader ;) */

        action(unit, terrain);
    }

    // add functions to this
    public List<Action<IUnit, ITerrain>> Actions = new List<Action<IUnit, ITerrain>>();
}

您可以使用反射來檢查傳入的每個Action的泛型參數,然后選擇與給定的單位和地形匹配的最大派生參數,然后調用該函數。 添加到Actions的功能可以在任何地方,甚至分布在多個程序集中。

有趣的是,我曾經遇到過這個問題幾次,但從來沒有超出游戲的背景。

將交互規則與Unit和Terrain類分離; 交互規則比這更通用。 例如,可以使用散列表,其中鍵是一對交互類型,並且值是對這些類型的對象進行操作的“效應器”方法。

當兩個對象必須交互時,在哈希表中找到所有交互規則並執行它們

這消除了類間依賴關系,更不用說原始示例中的可怕switch語句了

如果性能成為問題,並且交互規則在執行期間不會更改,則在遇到類型對時緩存規則集並發出新的MSIL方法以一次性運行它們

這里肯定有三個對象:

1)地形
2)地形效應
3)單位

我不會建議創建與地形/單位作為重點來查找行動地圖。 這將使您難以確保隨着單位和地形列表的增長而覆蓋所有組合。

實際上,似乎每個地形單元組合都具有獨特的地形效果,因此您可以從擁有共同的地形效果列表中看到一個好處是值得懷疑的。

相反,我會讓每個單位保持自己的地形地圖到地形效果。 然后,地形可以調用Unit-> AffectUnit(myTerrainType),單位可以查看地形對自身的影響。

老想法:

創建一個類iTerrain和另一個類iUnit,它接受一個地形類型的參數,包括影響每個單元類型的方法

例:

  boat = new iUnit("watercraft") field = new iTerrain("grass") field.effects(boat) 

好吧忘了我有更好的主意:

使每個地形的效果成為每個單元的屬性

例:


public class hovercraft : unit {
    #You make a base class for defaults and redefine as necessary
    speed_multiplier.water = 1
}

public class boat : unit {
    speed_multiplier.land = 0
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM