Pregunta

I have a base context BattleUnit derived from Unit which has two state Idle and Engaging. And these two states will have a reference to the instance of BattleUnit in order to handle the state. From BattleUnit I derived a Troop which is movable thus having two additional states Marching and Chasing. So if my State is designed as:

public abstract class State {
    protected Unit unit;
    public abstract void Handle();
}

In Idle and Engaging state I want to handle a instance of BattleUnit. I can attack or just stay idle. But I need to manipulate the interface of BattleUnit which may not exit in Unit. And similarly I want to handle a instance of Troop:BattleUnit in Marching or Chasing.

So can I just cast the Unit to the class I expect? i.e:

public class Engaging : State {
    public void Handle() {
        Troop trp = (Troop)unit;
        // trp.troopMethod() which is not virtual
    }
}

PS: I have a instance of State in myUnit like:

public class Unit{
    private State state;
    //...
}

Is there a better way?

¿Fue útil?

Solución

One approach you could try is having you states work for a specific type of entity, for example states to manipulate a BattleUnit and states to manipulate a Troop. So, for example you would start off with a base state similar to:

public abstract class State<T> where T : Unit
{
    protected State(T unit)
    {
        this.Unit = unit;
    }

    protected T Unit { get; private set; }
    public abstract void Handle();
}

You would then create the Engaging and Idle states for you BattleUnit:

public class EngagingState : State<BattleUnit>
{
    public EngagingState(BattleUnit unit)
        : base(unit)
    {
    }

    public override void Handle()
    {
        //  Here you can implement the "engaging" logic for a BattleUnit.
        this.Unit.X = ????;
        this.Unit.Y = ????;        
    }
}

You would then do the same for the Idle state:

public class IdleState : State<BattleUnit>
{
    ...
}

These two states could be applied to any instance that derives from BattleUnit (assuming BattleUnit is not abstract!).

For you Troop class you would create two classes called MarchingState and ChasingState and the structure would be similar to the classes outlined above, but the base class would be State<Troop>. In the Handle method you would perform the "Marching" and "Chasing" logic for a Troop instance. Now if you later decided that you would like a "Chasing" state for a vehicle, then you could create the following:

public class VehicleChasingState : State<Vehicle>
{
    public VehicleChasingState(Vehicle unit)
        : base(unit)
    {
    }

    public override void Handle()
    {
        //  Here you can implement the "chasing" logic for a Vehicle.
        this.Unit.X = ????;
        this.Unit.Y = ????;

        if(this.Unit.HasRadar)
        {
            //  Do special "chasing" logic when the vehicle has radar.
        }
    }
}

This class would then accept a Vehicle instance in the constructor and the Handle method would perform the "Chasing" logic for a vehicle. One reason for having different states for different entities, even if the purpose of the state is similar, is to allow you to implement special logic within your state, e.g. in this case the "Chasing" logic for a Vehicle checks for the existence of radar, but in the "Chasing" state for a Troop you would not want to do this (in this example anyway :)).

EDIT 1:

@zoujyjs ... In answer to your comment there are really two ways to solve this:

The first would be to create a IState interface that will contain a void Handle() method and would be implemented by the base State<T> class. Your Unit class would then have a protected IState CurrentState { get; set; } property. Each of the derived classes, at some point, would assign a new state to this property:

this.CurrentState = new EngagingState(this); //  ...this is inside a BattleUnit instance

//  or

this.CurrentState = new MarchingState(this); //  ...this is inside a Troop instance

Then at some point the Unit class would simply call this.CurrentState.Handle(). The main point of having an IState, without generic parameters, is the Unit class doesn't need to know the type of instance being managed by the state. All it needs to know is it has to call the Handle method. The added benefit of having the generic parameter on the State<T> class is to make it easier to implement logic.

But, the generic parameter is not necessary which leads me onto the second option:

You could remove the generic parameter and the Unit property from the base State class and each derived state would still accept a concrete type, i.e. BattleUnit or Troop, but instead of passing it onto the base State class it would hold a reference as a member variable. Therefore your Handle methods would look somethig like:

public override void Handle()
{
    _unit.X = ????;
    _unit.Y = ????;
} 

Your Unit class can then hold a reference to State and you can ignore the need for the IState interface mentioned above.

Otros consejos

How about making State accept a generic parameter of type Unit?

public abstract class State<out T> where T : Unit
{
   protected T unit;
   public abstract void Handle();
}

public class Engaging : State<Troop> {
  public void Handle() {
    unit.troopMethod();
}
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top