2011-04-16 16 views
2

給定一個類層次結構,如:如何避免具有繼承性的不可變數據模型中的代碼重複?

Entity { id, name, position } 
Combatant : Entity { health, strength } 
Avatar : Combatant { connection } 

裏面全是不可改變的。

要在一個實體上實現'移動',我可以返回一個新實體的位置不同。

Entity Move(p) { return new Entity(id, name, p); } 

但是,如果我在頭像上調用'移動',我會得到一個實體,而不是頭像。所以我必須在所有不可變類上實現'移動'。有沒有辦法避免這種情況,或更好的解決方案?

回答

3

你可以使用泛型解決這個問題,我假設爲簡單起見,有保護的setter方法的所有屬性:

Entity<InheritingType> 
    where InheritingType : Entity<InheritingType> 
{ 

    public T Move(Position newPosition) 
    { 
     T result = this.Clone(); 
     result.Position = newPosition; 
     return result; 
    } 

    private T Clone() 
    { 
    //create a new instance of ourselves using reflection 
    //i.e. reflect all the protected properties in the type (or fields if you don't want  even protected properties) , and set them 
    //you could also have the Clone method be abstract and force it's implementation in all inheriting types 

    } 
} 

要允許電流類型保持原樣,你可以做一個通用基礎的簡單的繼承爲每個具體類型:

Entity : Entity<Entity>{} 
Combatant<InheritingType> : Entity<InheritingType>{} 
Combatant : Combatant<Combatant>{} 
Avatar : Combatant<Avatar>{} 

爲深克隆一個例子,你可以follow this link但我要指出的是,如果性能很重要,它會更好,要求每個繼承類OVERR找到這種方法並將其自己的屬性添加到克隆過程中。

+0

本質上,你建議我克隆和設置,它工作得很好,沒有任何泛型...我錯過了泛型部分重要的東西? – 2011-04-17 03:16:12

+1

@Timothy - 您可以不使用泛型,但返回類型將是實體,而不是具體的子類型。你必須做一個不安全的演員,通過使用上述方法可以避免 – NightDweller 2011-04-17 09:08:01

+0

哦,我明白了。我並不太在意返回類型,只要對象被保留,但要牢記在心,謝謝! :)回想起來,我認爲我的問題有點被誤導,因爲真正絆倒我的是被返回的對象,而不是函數簽名。 – 2011-04-19 00:24:45

0

您需要將運動的邏輯與模型分離。 (始終堅持SOLID原則)。其餘類同NightDweller的帖子

你的代碼可能是這樣的:

pubilc interface IMovementLogic<T> where T:Entity 
{ 
    T Apply(Position p); 
    //You can name the method anything else you like such as "Move" or "execute 
} 

public class EntityMovement : IMovementLogic<Entity> {...} 
public class CombatantMovement : IMovementLogic<Combatant> {...} 
public class AvatarMovement : IMovementLogic<Avatar> {...} 

public class EntityMovement<T> : IMovementLogic<T> where T:Entity {...} 
public class CombatantMovement : EntityMovement<Combatant> {...} 
public class AvatarMovement : EntityMovement<Avatar> {...} 

然後實現此接口爲你的類的ECH。

根據運動的算法,你也可以考慮使用裝飾模式。

0

在試圖在一些業餘愛好項目中廣泛使用不可變類型後,我得出結論,在C#中,除了以下特例外,它們還有相當多的努力:類型是一個結構或一個密封類繼承object,並且沒有字段是集合。

在所有其他情況下,我認爲不可變類型比在C#中值得的更麻煩,不幸的是,儘管我更願意使用它們。

你確定你希望這個類的層次結構是不可變的嗎?

這已經很棘手了,當你添加屬性/屬性是一個集合的時候,你的困難將會在屋頂上射擊。例如,除非你非常仔細地做,否則將不得不創建深藏的藏品。但即使你對.Move足夠小心,在集合中替換單個元素的單個屬性也一定會要求複製整個集合。等...

+0

感謝您的意見。我使用不可變類的原因是支持時間旅行。使用持久性地圖我保留以前的狀態歷史。通過FSharpMap(來自Microsoft.FSharp.Core)和其他有用的不可變數據結構,處理集合一直是非常簡單的部分。到目前爲止,它使用簡單的實體工作得非常好,直到我遇到類層次狀態更改的複雜性。 – 2011-04-17 01:23:27

+0

@Timothy我會在下次看到FSharpMap的時候感覺自己寫了一些防範代碼的愛好 - 以前沒有碰到過。 – enverpex 2011-04-17 01:31:45