17. Design mode – status mode

Time:2021-9-10

When it comes to state mode, as the name suggests, it should be a state related design mode. However, we are the same as before. First, no matter what the state mode is, let’s start with a small example to see what problems the state mode can solve for us.

Example

Now we need to implement a traffic light dispatcher. The color of traffic lights needs to be cycled between red light – > green light – > yellow light – > red light, but green light – > red light or yellow light – > green light are not allowed. This belongs to the common sense of traffic rules. Now we use programs to realize it. Let’s first look at our most traditional thinking and implementation methods.

First, we can easily think of the need to define an enumeration of traffic light colors:

public enum LightColor
{
    Green,
    Red,
    Yellow
}

Then, define a traffic light class to process color conversion and corresponding business logic in the traffic light class. The code is as follows:

public class TrafficLight
{
    private LightColor _lightColor;
    public TrafficLight()
    {
        _lightColor = LightColor.Red;
    }

    public void Turn()
    {
        if (_lightColor == LightColor.Red)
        {
            Console.writeline ("red stop");
            _lightColor = LightColor.Green;
        }
        else if (_lightColor == LightColor.Green)
        {
            Console.writeline ("green line");
            _lightColor = LightColor.Yellow;
        }
        else if (_lightColor == LightColor.Yellow)
        {
            Console.writeline ("the yellow light is on, wait a minute");
            _lightColor = LightColor.Red;
        }
    }
}

Finally, you might as well call and run:

static void Main(string[] args)
{
    TrafficLight light = new TrafficLight();
    light.Turn();
    light.Turn();
    light.Turn();
    light.Turn();
}

Obviously, this code fully meets the requirements, with rigorous logic and extremely simple calling method. If the requirements remain unchanged, this may be the best implementation. But after the edification of the previous design principles, we know that it is impossible to keep the demand unchanged. Therefore, we can easily find that the code is full of problemsif-elseConditional branching, which means that expansion is difficult. The example here is simple and may not be obvious, but there must be more conditional branches and more similar in real projectsTurn()This will make it extremely difficult for the whole project to expand and maintain, because it seriously violatesOpening and closing principle

In fact, for solvingif-elseorswitch-caseWe have quite experienced the problems. In the simple factory pattern, we use the factory method pattern to abstract the factory classes that produce specific classesswitch-caseIn the policy pattern of the previous article, we also solved the problem by abstracting methods into policy classesswitch-caseProblems. Here is no exception. We must abstract something. But what is the concrete abstraction? The color of the light?Turn()method? Or something? The idea doesn’t seem so clear. However, we found that the code structure is very similar to the example before the transformation of the policy mode. We might as well use the policy mode to see if it can meet the needs. If not, see what are the shortcomings, and then make further transformation, because we know that the policy mode can at least solve the problemsif-elseorswitch-caseProblems.

Let’s look at the code after the transformation of the policy mode. FirstTurn()Method is abstracted into a policy class:

public interface ITurnStrategy
{
    void Turn();
}

public class GreenLightTurnStrategy : ITurnStrategy
{
    public void Turn()
    {
        Console.writeline ("green line");
    }
}

public class RedLightTurnStrategy : ITurnStrategy
{
    public void Turn()
    {
        Console.writeline ("red stop");
    }
}

public class YellowLightTurnStrategy : ITurnStrategy
{
    public void Turn()
    {
        Console.writeline ("the yellow light is on, wait a minute");
    }
}

Look at the transformedTrafficLightClass:

public class TrafficLight
{
    private ITurnStrategy _turnStrategy;

    public TrafficLight(ITurnStrategy turnStrategy)
    {
        _turnStrategy = turnStrategy;
    }

    public void Turn()
    {
        if (_turnStrategy != null)
        {
            _turnStrategy.Turn();
        }
    }

    public void ChangeTurnStrategy(ITurnStrategy turnStrategy)
    {
        _turnStrategy = turnStrategy;
    }
}

Everything seems perfect and seamless. Let’s see how to use:

static void Main(string[] args)
{ 
    TrafficLight light = new TrafficLight(new RedLightTurnStrategy());
    light.Turn();
    light.ChangeTurnStrategy(new GreenLightTurnStrategy());
    light.Turn();
    light.ChangeTurnStrategy(new YellowLightTurnStrategy());
    light.Turn();
    light.Turn();
}

The problem is found as soon as it is used, and the call becomes complex. In fact, in order to make the system easier to expand, it’s nothing complicated when calling. However, another fatal problem can’t be ignored. We hope that the lamp color switching is controlled by an internal fixed mechanism, not the caller. If the user changes the color he wants, wouldn’t the traffic rules be chaotic? Obviously, the strategic model does not meet the needs. In fact, we hope thatlight.ChangeTurnStrategy()This action is completed internally by the system itself.

Since the demand is not met, what is the problem? Looking back and combing it again, we found that perhaps there was a deviation in our thinking at the beginning. Can the traffic lights change colors? Obviously not, because the color of each lamp is fixed. What we call changing the color is actually changing the lamp. Should we use the factory method mode to create lamps with different colors? Obviously, it is not appropriate. The three lights are there at the beginning. They are just cyclic switching. There is no creation process. In fact, maybe we should change our thinking. What is obviously reflected here is the three states of traffic lights. Each state corresponds to a behavior that needs to be handled. At the same time, only the state has the process of switching.

After changing the way of thinking, we look at the problem from a different perspective. Look at the code after changing the way of thinking:

public abstract class TrafficLightState
{
    public abstract void Handle(TrafficLight light);
}

public class GreenState : TrafficLightState
{
    public override void Handle(TrafficLight light)
    {
        Console.writeline ("green line");
        light.SetState(new YellowState());
    }
}

public class RedState : TrafficLightState
{
    public override void Handle(TrafficLight light)
    {
        Console.writeline ("red stop");
        light.SetState(new GreenState());
    }
}

public class YellowState : TrafficLightState
{
    public override void Handle(TrafficLight light)
    {
        Console.writeline ("the yellow light is on, wait a minute");
        light.SetState(new RedState());
    }
}

public class TrafficLight
{
    private TrafficLightState _currentState;

    public TrafficLight()
    {
        _currentState = new RedState();
    }

    public void Turn()
    {
        if (_currentState != null)
        {
            _currentState.Handle(this);
        }
    }

    public void SetState(TrafficLightState state)
    {
        _currentState = state;
    }
}

As like as two peas can be found, in addition to the change of class name and method name, the code is almost the same as the policy mode (the concrete evolution process is difficult to express clearly, and the video on my B station or official account) can be seen. But the meaning is far from the sky. This is not a direct abstraction of the method into a policy object, but an abstract state of abstraction, so the abstract class is used. Instead of interfaces (interfaces can also be used, but we usually abstract methods into interfaces and objects or attributes into classes); It also provides interface methods for each state to handle the corresponding behavior in that state, rather than directly providing interface methods for specific behavior.

In addition, the parameters are different,TrafficLightStateNeed to hold rightTrafficLightBecause it needs to be handled in a specific state classTrafficLightState transition. The modified code perfectly meets the requirements again, the caller becomes simple again, and the state transfer returns to sovereignty again:

static void Main(string[] args)
{
    TrafficLight light = new TrafficLight();
    light.Turn();
    light.Turn();
    light.Turn();
    light.Turn();
}

That’s itState modeFinally, the following is the final class diagram of the traffic light example:

17. Design mode - status mode

Finite state machine

From the above example, we may easily associate state machines. We often hear or see words such as finite state machines or infinite state machines. What is the relationship between finite state machines and state patterns? Let’s first look at how a finite state machine works. The working principle of the finite state machine is thatEventAfter that, according toCurrent state (cur_state), decided to implementAction, and setNext state (nxt_state)。 As can be seen from the example of traffic lights,EventnamelyTrafficLightMediumTurn()Method is triggered by the client. After triggering, the system will judge which lamp state it is currently in, and then execute the corresponding action. After completion, the next lamp state will be set, which perfectly corresponds to the working principle of the finite state machine. So, are they equivalent? In fact, the state mode is only a means to realize the finite state machine, becauseif-elseThe implementation of version is also a finite state machine.

This is an episode. Let’s return to the state mode.

definition

State mode allows an object to change its behavior when its internal state changes, so that the object appears to have modified its class.

UML class diagram

By abstracting the class diagram of the traffic light example, we can get the followingState modeClass diagram of:

17. Design mode - status mode

  • Context: context environment, define the interface required by the client program, maintain an instance of a specific state role, and delegate state related operations to the currentConcreteStateObject to handle;
  • State: abstract state, which defines the interface of the behavior corresponding to a specific state;
  • ConcreteState: concrete state, which implements the interface defined by abstract state.

Advantages and disadvantages

advantage

  • solveswitch-caseif-elseIt is obvious that there is nothing to say about the problems that are difficult to maintain;
  • Clear structure, improved scalability, not difficult to find,ContextThe class is concise and clear. When expanding, it hardly needs to change, and each state subclass is also concise and clear. When expanding, it only needs little change.
  • The state can be shared among multiple contexts through singleton or shared element.

This problem needs to be said separately. It is not difficult to find that although the state mode solves many problems, a new state class needs to be created every time the state is switched. Originally, it was just a small enumeration value. In this comparison, is the cost of repeatedly creating objects too huge? In fact, to solve the problem of repeated creation of objects, we know that single instance mode and meta sharing mode are good choices. Which one to choose depends on the number of state classes and personal preferences.

The following is the code improved by using the Xiangyuan mode. The first is the familiar Xiangyuan factory. The code is very simple:

public class LightStateFactory
{
    private static readonly IDictionary<Type, TrafficLightState> _lightStates
           = new Dictionary<Type, TrafficLightState>();

    private static readonly object _locker = new object();
    public static TrafficLightState GetLightState<TLightState>() where TLightState : TrafficLightState
    {
        Type type = typeof(TLightState);
        if (!_lightStates.ContainsKey(type))
        {
            lock (_locker)
            {
                if (!_lightStates.ContainsKey(type))
                {
                    TrafficLightState typeface = Activator.CreateInstance(typeof(TLightState)) as TrafficLightState;
                    _lightStates.Add(type, typeface);
                }
            }
        }

        return _lightStates[type];
    }
}

It is easier to use. Just replace the place where the status object is created with a meta factory. The code fragment is as follows:

public override void Handle(TrafficLight light)
{
    Console.writeline ("red stop");
    light.SetState(LightStateFactory.GetLightState<GreenState>());
}

It should be mentioned here that since the state is single instance, it can be shared among multiple contexts, and concurrency must be considered whenever global sharing is involved. Therefore, unless sharing is explicitly required, other resources should not be held in the state class, otherwise concurrency problems may occur. For the same reason, state classes should not hold pairs through attributes or fieldsContextThis is also what I use local variable pairsTrafficLightReasons for parameter transmission.

shortcoming

  • With the expansion of state, the number of state classes will increase. This is an old saying. Almost all design patterns that solve similar problems have this disadvantage;
  • It increases the complexity of the system, and improper use will lead to logical confusion, because the number of state classes has increased after all, and it also involves the transfer of state, so the thinking may be more chaotic;
  • It does not fully meet the opening and closing principle, because during extension, in addition to adding or deleting the corresponding state subclasses, it is also necessary to modify other state classes related to the corresponding state transition, but it has been much improved compared with the original implementation.

Difference from policy mode

Strategy mode

  • Emphasize interchangeable algorithms;
  • The user directly interacts with the specific algorithm to determine the replacement of the algorithm, and needs to understand the algorithm itself;
  • Policy classes do not need to holdContextReference to.

State mode

  • Emphasize changing the internal state of the object to help control their behavior;
  • Status is the internal flow of objects. Users do not directly interact with the status and do not need to understand the status itself;
  • Status class needs to be heldContextIs used to implement state transition.

summary

In fact, it can be seen from the class diagram and implementation mode that the state mode is really similar to the policy mode, but because the policy mode is more general, it is easier to think of. Moreover, we also know that both state mode and policy mode can solve the problemif-elseThe key to the problem lies in the identification of strategies and states. As in the above traffic light example, it is difficult to find anything wrong when it is identified as a strategy at the beginning. To give a more popular example, teachers will give different reward and punishment schemes to students according to their test scores. For example, students with scores lower than 60 will be fined, and students with scores higher than 90 will be rewarded with money. However, how to reward and how to punish is decided by teachers (otherwise, teachers will cry if they score more than 90 in the full test). This is an ordinary conditional branch without enumeration, but we can still see that it reflects that different strategies can be adopted according to different score segments, and the strategy mode can be adopted. For another example, for the same test results, your parents set an index for you. If you get below 60, you will be fined. If you get more than 90 points, you will be rewarded. Is this strategy or state? It seems that everything is OK, but in fact, if you think carefully, you will find that it may be better to regard it as a state, that is, there will be a corresponding action in different states, but what are the States? Fractional segment? Reward and punishment? How does the state transfer? You have to think carefully. The example here is simple. You may be able to figure it out (not necessarily), but in the actual project, it is estimated that it is not so easy.

However, once we recognize the state, and then recognize that the state transition will occur according to certain trigger conditions, the state mode can be used in nine cases out of ten.

Source code link
More content, welcome to the official account:
17. Design mode - status mode