2017-04-05 47 views
2

基類屬性重寫爲派生類型和訪問想象以下類:通過基類

public abstract class State {} 

public abstract class View : MonoBehaviour { 
    public State state; 
} 

現在我想從兩個類派生創建StateAItemA

public class StateA { // some properties } 

public class ViewA : View { 
    public StateA state; 
} 

ViewA應該只接受StateA作爲其狀態。設置一個假想的StateB應該是不可能的。當我試圖設置的ViewA

View view = someGameObject.GetComponent<View>; 
view.state = new StateA(); 

只有基類的狀態的狀態將被設置。在我的情況下投射到ViewA並不是真的可能,因爲它必須動態投射。

現在我想出了兩種可能的解決方案。

解決方案1 ​​ - 類型檢查和投

所有狀態類保持不變。基類正在檢查狀態的正確類型。

public abstract class View : MonoBehaviour { 
    public State state; 
    public Type stateType; 

    public void SetState (State state) { 
     if (state.GetType() == this.stateType) { 
      this.state = state; 
     } 
    } 
} 

要訪問ViewA狀態我這樣做:

(StateA)this.state 

解決方案2 - 團結路

State將改爲從MonoBehaviour派生。

public abstract class State : MonoBehaviour {} 

要將狀態添加到項目中,我只想一個新StateA組件添加到遊戲對象。該項然後用GetComponent加載該狀態來訪問它。

viewA.gameObject.AddComponent<StateA>(); // set state with fixed type 
viewA.gameObject.AddComponent (type);  // set state dynamically 

this.gameObject.GetComponent<StateA>(); // get state in item class 

結論

這兩種解決方案都沒有在我看來,理想的。你們知道有更好的方法來做這種事嗎?我希望你明白我想要達到的目標。期待看到你的想法。

編輯

似乎過於簡單化我我的問題。爲了弄清楚爲什麼我這樣做,在UI中將Item視爲ViewStateA沒有任何功能。相反,它只是讓視圖知道它應該做什麼的一些屬性。例如,對於表格視圖,狀態將包含要顯示的事物的集合。

public class StateA : State { 
    SomeObject[] tableViewData; // which data to load into the table view 
} 

或換另一種觀點。

public class StateB : State { 
    bool hideButtonXY; //should I hide that button? 
} 

在另一大類我把最後的歷史表現出與他們各自的國家的意見。這樣我就可以通過普通的網頁瀏覽器實現來回功能。 我希望這可以讓我的意圖清晰。

+0

當你在'Item'中使用它時,你有什麼理由需要知道'State'實例的具體子類? – Serlite

+1

(就像...在什麼情況下阻止你從ItemA中存儲'State'類型的變量,而不是'StateA'?) – Serlite

+0

將特定的實現結合在一起通常不是一個好主意,你能否包括爲什麼您需要在示例中這樣做? – CaTs

回答

2

這是一個相當普遍的問題,它是由MonoBehaviours需要在Unity中使用的奇怪方式和常見的繼承錯誤應用相結合造成的。這不是你可以解決的問題,而是你爲了避免而設計的。

這一切都取決於你爲什麼需要狀態的子類,但我假設它是這樣的:base state有一些基本邏輯,並且派生的stateA有一些新的/修改的屬性/邏輯。假設這是真的:

  • 保留所有項目子類使用對State的相同引用。因爲它是一個子類,所以可以將它保存到基類型的屬性中。
  • 製作一組標準的訪問器/字段/方法,Item子類將始終訪問State子類。示例:DoThing(),ProcessState()等
  • 使這些訪問器/字段/方法具有共同的代碼,將總是在密封的函數中運行。
  • 使上述方法調用內部 /定義您需要運行的任何自定義邏輯的受保護方法。如果你想提供它們的標準實現,這些應該是抽象的,或者是虛擬的。
  • 使所有子類只覆蓋抽象/虛擬方法,然後他們可以訪問任何自定義代碼。
  • 當你調用stateStoredAsBaseClass.DoThing()時,它會自動調用在子類中被覆蓋的stateStoredAsBaseClass.DoThingInternal(),因此你的自定義StateA代碼就在其中。

示例代碼寫出來:

abstract class State { 
    public int sharedField; 

    public sealed void DoThing() { 
     sharedField = 1; 

     DoThingInternal(); 
    } 

    protected abstract void DoThingInternal(); 
} 

abstract class Item { 
    State state; 

    public abstract void TestMethod(); 
} 

class StateA : State { 
    public int customField = 10; 

    protected override void DoThingInternal() { 
     sharedField = 10; 
    } 
} 

class ItemA : Item { 

    public ItemA() { 
     //read from somewhere else, pass it in, etc. 
     state = new StateA(); 
    } 

    public override void TestMethod() { 
     state.DoThing(); 
     Console.WriteLine(state.sharedField); //will print 10 
    } 
} 

這是一個很常見的模式,尤其是在團結,當你想使用繼承這樣。這種模式在Unity之外也可以很好地工作,並且避免了泛型,因此對於一般情況而言是有用的。

注意:公共方法的密封屬性點是爲了防止其他人/你以後試圖隱藏它(public new void DoThing()或者public void DoThing())然後被混淆當它不起作用。

如果你要覆蓋它,然後因爲你通過基地類調​​用它,它會調用基地版本的功能,而不是你的新方法。將其定義爲抽象/虛擬並由基類本身調用可以解決這個問題。這是否意味着你已經有效地定義:

interface IState { 
    public int sharedField; 

    public void DoThing(); 
} 

那麼你也許可以看到同樣的結果可以用一個接口來實現,注重落實。

編輯以符合例子問題:

abstract class State { 
    public sealed Button[] GetHiddenButtons() { 
     GetHiddenButtonsInternal(); 
    } 

    public sealed object[] GetObjects() { 
     GetObjectsInternal(); 
    } 

    protected abstract Button[] GetHiddenButtonsInternal(); 
    protected abstract object[] GetObjects(); 
} 

abstract class Item { 
    State state; 

    public abstract void Render(); 
} 

class StateA : State { 
    public Button[] _hiddenButtons; 
    public object[] _objects; 

    public StateA() 
    { 
     //get hidden buttons from somewhere/pass them in/create them. 
     _hiddenButtons = new Button[0]; 

     //get objects from somewhere/pass them in/create them. 
     _objects = new object[0]; 
    } 

    protected override Button[] GetHiddenButtons() { 
     return _hiddenButtons; 
    } 

    protected override object[] GetObjects() { 
     return _objects; 
    } 
} 

class ItemA : Item { 

    public ItemA() { 
     //read from somewhere else, pass it in, etc. 
     state = new StateA(); 
    } 

    public override void Render() { 
     //get objects 
     var objects = state.GetObjects(); //returns array from StateA 

     //get hidden buttons to hide 
     var hiddenButtons = state.GetHiddenButtons(); 
    } 
} 

基本上,你在做什麼是創建一個接口是通用的,足以滿足任何你需要你的國家做。該模式沒有規定如何返回它們,因此您的GetHiddenButtons()方法可以:返回ID列表,返回對象,聯繫GameObject以獲取列表,返回硬編碼列表等。

這種模式可以讓你做到你想做的事情,你只需要確保基礎狀態的設計一般足以讓你做到這一點。

+0

擴大了答案的位數以解釋與接口的相似性,並警告如何修改覆蓋中的「密封」方法(如果未使用密封)。 –

+0

謝謝你的回答。這是一個我不知道的有趣模式。因爲我更新了我的問題,所以我不太想做什麼。不過謝謝。 – Drexxgnom

+0

我的回答適合您更新的問題。基類將有一個名爲GetObjects的方法或其他方法,並且不會有共享字段。自定義類將覆蓋GetObjectsInternal,並有一些自定義字段以您想要的任何形式序列化並返回。 –