A simple example of the State design pattern

Let's understand all three participants with a simple example. Say, we want to implement a TV remote with a simple button to perform on/off actions. If the TV is on, the remote button will switch off the TV and vice versa. In this case, the State interface will define the method (say, doThis()) to perform actions such as switching on/off the TV. We also need to define ConcreteClass for different states. In this example, we have two major states, StartState and StopState, which indicate when the TV is switched on and the state in which the TV is switched off, respectively.

For this scenario, the TVContext class will implement the State interface and keep a reference to the current state. Based on the request, TVContext forwards the request to ConcreteState, which implements the actual behavior (for a given state) and performs the necessary action. So, in this case, the base state is StartState (as defined earlier) and the request received by the TVContext class is to switch Off the TV. TVContext class understands the need and accordingly forwards the request to StopState concrete class which inturn calls the doThis() method to actually switch off the TV:

from abc import abstractmethod, ABCMeta

class State(metaclass=ABCMeta):
    
    @abstractmethod
    def doThis(self):
        pass


class StartState (State):
    def doThis(self):
        print("TV Switching ON..")


class StopState (State):
    def doThis(self):
        print("TV Switching OFF..")


class TVContext(State):
    
    def __init__(self):
        self.state = None
    
    def getState(self):
        return self.state
    
    def setState(self, state):
        self.state = state
    
    def doThis(self):
        self.state.doThis()


context = TVContext()
context.getState()
start = StartState()
stop = StopState()

context.setState(stop)
context.doThis()

Here is the output for the preceding code:

A simple example of the State design pattern

The State design pattern with v3.5 implementation

Let's now take a look at a real-world use case for the State design pattern. Think of a computer system (desktop/laptop). It can have multiple states such as On, Off, Suspend, or Hibernate. Now, if we want to manifest these states with the help of State design pattern, how will we do it?

Say, we start with the ComputerState interface:

  • The state should define two attributes, which are name and allowed. The name attribute represents the state of the object, and allowed is a list that defines the state's object, which it is allowed to get into.
  • The state must define a switch() method, which will actually change the state of the object (in this case, the computer).

Let's take a look at the code implementation of the ComputerState interface:

class ComputerState(object):
    name = "state"
    allowed = []
    
    def switch(self, state):
        if state.name in self.allowed:
            print('Current:',self,' => switched to new state',state.name)
            self.__class__ = state
        else:
            print('Current:',self,' => switching to',state.name,'not possible.')
    
    def __str__(self):
        return self.name

Let's now take a look at ConcreteState, which implements the State interface. We will define four states:

  • On: This switches on the computer. The allowed states here are Off, Suspend, and Hibernate.
  • Off: This switches off the computer. The allowed state here is just On.
  • Hibernate: This state puts the computer in the hibernate mode. The computer can only get switched on when it's in this state.
  • Suspend: This state suspends the computer, and once the computer is suspended, it can be switched on.

Let's now take a look at the code:

class Off(ComputerState):
    name = "off"
    allowed = ['on']

class On(ComputerState):
    name = "on"
    allowed = ['off','suspend','hibernate']

class Suspend(ComputerState):
    name = "suspend"
    allowed = ['on']

class Hibernate(ComputerState):
    name = "hibernate"
    allowed = ['on']

Now, we explore the context class (Computer). The context does two main things:

  • __init__(): This method defines the base state of the computer
  • change(): This method will change the state of the object, and the actual change in behavior is implemented by the ConcreteState classes (on, off, suspend, and hibernate)

Here is the implementation of the preceding methods:

class Computer(object):
    def __init__(self, model='HP'):
        self.model = model
        self.state = Off()
    
    def change(self, state):
        self.state.switch(state)

The following is the code for the client. We create the object of the Computer class (Context) and pass a state to it. The state can be either of these: On, Off, Suspend, and Hibernate. Based on the new state, the context calls its change(state) method, which eventually switches the actual state of the computer:

if __name__ == "__main__":
    comp = Computer()
    # Switch on
    comp.change(On)
    # Switch off
    comp.change(Off)
    
    # Switch on again
    comp.change(On)
    # Suspend
    comp.change(Suspend)
    # Try to hibernate - cannot!
    comp.change(Hibernate)
    # switch on back
    comp.change(On)
    # Finally off
    comp.change(Off)

Now, we can observe the following output:

The State design pattern with v3.5 implementation

__class__ is a built-in attribute of every class. It is a reference to the class. For instance, self.__class__.__name__ represents the name of the class. In this example, we use __class__ attribute of Python to change the State. So, when we pass the state to the change() method, the class of the objects gets dynamically changed at runtime. The comp.change(On) code, changes the object state to On and subsequently to different states like Suspend, Hibernate, and Off.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.222.3.255