将基类属性覆盖为派生类型,并通过基类

时间:2017-04-05 20:39:48

标签: c# inheritance unity3d 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组件添加到gameObject。然后该项使用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?
}

在另一个课程中,我保留了最后显示的视图与各自状态的历史记录。这样我就可以从普通的Web浏览器实现来回功能。 我希望这能使我的意图明确。

1 个答案:

答案 0 :(得分:3)

这是一个相当普遍的问题,它是由需要在Unity中使用的奇怪方式MonoBehaviours和常见的继承误用造成的。这不是你可以解决的问题,而是你为了避免而设计的东西。

这一切都取决于你为什么需要State的子类,但我假设它是这样的:base state有一些基本逻辑,派生的StateA有一些新的/修改过的属性/逻辑。假设这是真的:

  • 使用对State的相同引用保留所有Item子类。因为它是一个子类,所以可以将它保存到基类型的属性中。
  • 制作一组标准的访问者/字段/方法,这是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 new void DoThing()或public void DoThing())然后在它无法工作时感到困惑。

如果你要覆盖它,那么因为你通过 base 类调用它,它会调用函数的 base 版本而不是你的新版本方法。将它定义为抽象/虚拟并由基类调用本身就可以解决这个问题。这确实意味着您已经有效地定义了:

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以获取列表,返回硬编码列表等。

这种模式可以让你完成你想要做的事情,你只需要确保基本状态的设计通常足以让你这样做。