c# - 将基类属性重写为派生类型并通过基类访问

标签 c# inheritance unity-game-engine properties polymorphism

想象一下以下类:

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

结论

我认为这两种解决方案都不理想。你们知道有更好的方法来做这种事情吗?我希望你明白我想要实现的目标。期待看到您的想法。

编辑

看来我的问题过于简单化了。为了弄清楚为什么我这样做,请将 Item 视为 UI 中的 ViewStateA 没有任何功能。相反,它只是一些属性,让 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/

相关文章:

c# - 在 C# 中,我可以包装 Func 来缓存结果吗

c++ - 通过容器的 protected 继承来 boost foreach

c++ - 如何在不继承 A 类的情况下使 A 类成为 B 类的成员?

javascript - JavaScript 中的隐私

c# - 只读指定span

c# - ASP.NET Core 修改/替换请求体

android - 防止 Android 上的 Unity 在启动画面上检测用户输入?

iphone - Vuforia AR 按钮可以打开另一个 View 或应用程序吗?

c# - 如何从单独的类中提取字符串,同时将该类保留在我的主目录中的列表中(C#,Unity)

c# - 如何完全删除静态事件的所有项目