Tuesday 13 January 2015

DESIGN PATTERN BY EXAMPLE (IN RUBY) - THE STATE PATTERN

In my previous posts I've shown you an application of the Abstract Factory and the Singleton pattern.

In this post we are going to explore the State design pattern.

A typical scenario where the State design pattern is particularly helpful is when an object of our application needs to behave differently on the basis of its State.

In our example, the Person class defines three methods. Each of this methods will run some logic that is specific to the person state.

Here is the code for this example:


 class Person   
      def initialize   
           @age = 0  
           @state = :CHILD  
      end  
      def incr_age  
           @age+=1;  
           if (@age==18)  
                @state = :ADULT  
           end  
           if (@age==65)  
                @state = :PENSIONER  
           end  
      end  
      def vote()  
           if @state==:CHILD  
                puts "Too young to vote"  
           else  
                puts "Vote accepted"  
           end  
      end  
      def apply_for_buspass  
           if (@state==:PENSIONER)  
                puts "Pass granted"  
           else  
                puts "Too young for a bus pass"  
           end  
      end  
      def conscript  
           case @state  
            when :PENSIONER: puts "Too old to be conscripted"  
            when :CHILD: puts "Too young to be conscripted"  
            when :ADULT: puts "Here's your gun"  
       end  
  end  
 end  

Every time a method of the Person class is called, a status check is made.

This code is very convoluted. Adding more states means adding more "if" statements in every single method. Adding new behavior means implement a new "case" statement.
The nature of this problem, suggest the use of the State pattern.

The state pattern, which closely resembles Strategy Pattern, is a behavioral software design pattern, also known as the objects for states pattern. This pattern is used to encapsulate varying behavior for the same routine based on an object's state object. This can be a cleaner way for an object to change its behavior at runtime without resorting to large monolithic conditional statements

The first step is to create a class for each State a Person can acquire. 

We also create a superclass called State which could hold common behavior to all the states:


 class State  
  def vote  
   raise 'this method should be implemented in a concrete subclass'  
  end  
  def apply_for_buspass  
   raise 'this method should be implemented in a concrete subclass'  
  end  
  def conscript  
   raise 'this method should be implemented in a concrete subclass'  
  end  
  def apply_for_medical_card  
   raise 'this method should be implemented in a concrete subclass'  
  end  
 end  

I will show you only the state class for the ChildState:


 require_relative 'state.rb'  
 class ChildState < State  
  @@instance = ChildState.new  
  private_class_method :new  
  def self.instance  
   return @@instance  
  end  
  def vote  
    puts "Too young to vote"  
  end  
  def apply_for_buspass  
    puts "Too young for a bus pass"  
  end  
  def conscript  
     puts "Too young to be conscripted"  
  end  
  def apply_for_medical_card  
   puts 'qualified for medical card'  
  end  
 end  


The other one's will be very similar to this one but they will implement specific behavior based on the specific state.

If you read my post on the Singleton pattern, I am sure you recognized that I am also using the Singleton pattern in this example. The reason behind this decision is that we do not need to create a state object for each Person object we create. 
States this application is modelling at moment, are three by design, so we need to create only three states instances globally. Using the Singleton Pattern here, will insure that when we create a new State, only a single instance will be created and eventually retrieved. 
States in this application are stateless. 

It can also be useful to have an utility class that returns a specific state for a Person when an age is passed in. I will call this class AgeStateContext:
 require_relative 'adult_state.rb'  
 require_relative 'child_state.rb'  
 require_relative 'pensioner_state.rb'  
 require_relative 'teenager_state.rb'  
 class AgeStateContext  
  def AgeStateContext.state(age)  
   if (age>65)  
    return PensionerState.instance  
   end  
   if (age>18)  
    AdultState.instance  
   end  
   if (age>15)  
    TeenagerState.instance  
   end  
   return ChildState.instance  
  end  
 end  

Now the person class doesn't need to keep its state anymore.

It can just ask this utility class, which will answer with the state based on its age.
This is the new Person class


 require_relative 'age_state_context.rb'  
 require_relative 'state.rb'  
 class Person  
  def initialize  
   @age = 0  
  end  
  def incr_age  
   @age+=1  
  end  
  def vote()  
   AgeStateContext.state(@age).vote  
  end  
  def apply_for_buspass  
   AgeStateContext.state(@age).apply_for_buspass  
  end  
  def conscript  
   AgeStateContext.state(@age).conscript  
  end  
  def apply_for_medical_card  
   AgeStateContext.state(@age).apply_for_medical_card  
  end  
 end  

It looks much simpler and readable.
we can surely import the AgeStateContext class statically and avoid to name it every time we need to call the state(@age) method.

We create a simple main for exercising this application:


 p = Person.new  
 for i in 1..80  
  p.incr_age();  
  p.apply_for_buspass();  
  p.vote();  
  p.conscript();  
  p.apply_for_medical_card();  
 end  

This is the class diagram for this application:






HAPPY CODING !

LUCA

No comments:

Post a Comment