想象一下以下类:
public abstract class State {}
public abstract class View : MonoBehaviour {
public State state;
}
现在我想从这两个类派生来创建 StateA
和 ItemA
。
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
结论
我认为这两种解决方案都不理想。你们知道有更好的方法来做这种事情吗?我希望你明白我想要实现的目标。期待看到您的想法。
编辑
看来我的问题过于简单化了。为了弄清楚为什么我这样做,请将 Item
视为 UI 中的 View
。 StateA
没有任何功能。相反,它只是一些属性,让 View 知道它应该做什么。例如,对于 TableView ,状态将包含要显示的内容的集合。
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?
}
在另一个类中,我保留了最后显示的 View 及其各自状态的历史记录。通过这种方式,我可以从普通的网络浏览器实现来回功能。 我希望这能让我的意图清楚。
最佳答案
这是一个相当常见的问题,是由 Unity 中需要使用 MonoBehaviours 的奇怪方式和继承的常见误用共同导致的。这不是您可以解决的问题,而是您通过设计来避免的问题。
这一切都取决于为什么你需要 State 的子类,但我假设它是这样的:基本 State 有一些基本逻辑,派生的 StateA 有一些新的/修改的属性/逻辑。假设这是真的:
- 使所有 Item 子类使用对 State 的相同引用。因为它是子类,所以您可以将其保存到基类型的属性中。
- 制定一组标准的访问器/字段/方法,这是 Item 子类始终访问 State 子类的方式。示例:DoThing()、ProcessState() 等
- 使这些访问器/字段/方法具有在密封函数中始终运行的通用代码。
- 使上述方法调用一个内部/ protected 方法,该方法定义您需要运行的任何自定义逻辑。如果您想提供它们的标准实现,这些应该是抽象的或虚拟的。
- 使所有子类仅重写抽象/虚拟方法,然后它们就可以访问任何自定义代码。
- 当您调用 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 中,当您想像这样使用继承时。这种模式在 Unity 之外也能很好地工作,并且避免了泛型,因此了解一般情况很有用。
注意:公共(public)方法上的 seal 属性的目的是防止其他人/您自己稍后尝试隐藏它(public new void DoThing() 或只是 public void DoThing())然后当它不起作用时感到困惑。
如果您要重写它,那么因为您通过 base 类调用它,所以它会调用该函数的 base 版本,而不是您的新方法。将其定义为抽象/虚拟并由基类本身调用可以解决这个问题。这确实意味着您已经有效地定义了:
interface IState {
public int sharedField;
public void DoThing();
}
因此您可能会发现,通过接口(interface)并注意实现可以实现相同的结果。
编辑以使示例与问题一致:
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();
}
}
基本上,您正在做的就是创建一个足够通用的接口(interface),可以满足您需要状态执行的任何操作。该模式没有规定您需要如何返回它们,因此您的 GetHiddenButtons() 方法可以:返回 ID 列表、返回对象、联系 GameObject 以获取列表、返回硬编码列表等。 p>
这种模式允许您准确地做您想做的事情,您只需确保基本状态的设计足够通用以允许您做到这一点。
关于c# - 将基类属性重写为派生类型并通过基类访问,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43241213/