State design pattern for beginners.
Background –
There is a very thin line difference between command design pattern and state design pattern, if
you look at them from a very high level, they may look alike. Once you read this blog, you will be able to differentiate between them.
State design pattern observe the states of the object. We
will have different states of objects during execution, each state has its unique
behavior. So if we watch them closely. We will understand that it is a 1 to 1
mapping between different states and their responses.
Let me explain it through an example – think of a class who
is having 3 bool data members. If I ask you how many different state (or say
behavior) it can have. The answer is simple – 8, see below –
It means in general, if we have
n number of bool data members, then we can have 2^n different states. Or say 2^n
different behaviors. In general, we have more complex data members then bool, hence complexity increases exponentially.
Why we need state design pattern –
Above mentioned example was tiny, think of a
controller system of nuclear power plants. Where we have more than 100
parameters to set many different behaviors, so you will have more than 2^100
different states to deal with.
We have added more complexity here, how to address that? Any
controller class, who has to handle these many states. And divert the code flow
according to change in parameter of plant will become a challenging class. That
class may become the buggiest part of your system. And full understanding of such class will become “the task.”
for any developer.
Now think, in coming future, I have added one more parameter
in our plant controlling system. And now, the controller has to update the logic. There will be so much difficulty and substantial code changes required, which has to go
through regression testing. Whenever I add a new parameter. This is not acceptable. It will increase maintenance costs.
But through state design pattern, we can eliminate
complexity.
State design pattern –
We create different class per state. One class will be
responsible for one state of the object. If we added new parameters into the
system, we will create all together a new classes instead of disturbing our old written
classes. We will reduce testing costs and will follow the open-close design
principle as well.
Think about an ATM machine, you would have used it so many
times. One ATM machine is like one existing object, now how it will behave, will
depend on the states. For example –
1.
No card inserted – machine will act static with
no operation.
2.
You inserted the card – state of machine has
changed, it will show some options.
3.
You asked for a pin number.
4.
You inserted a pin, if correct, again state of
ATM get changed.
5.
You withdraw money.
Etc….
There are many states an ATM has (but finite number of
states, it could be many, but limited). All different states are different behaviors
and have its own functionality to execute. Withdraw is a behavior or state of
ATM, and it has to perform certain operations to get that work done. We
write them into a different class in state design pattern.
So depending upon the internal state, we change the behavior!
Simple :)
Difference between command design pattern and state design pattern –
1.
When request is based on transaction-based
system, then we use command design pattern (for example – in net banking 3rd
party transaction is one transaction, checking balance is another transaction,
closing account is another transaction..etc.).
2.
When there is no transaction/business logic, you have
to create an object (with default settings) and change its behavior as an internal state of the object changes, then we use state design pattern. For example, any
controller system of a plant, airport, monitoring system, nuclear power plant, etc. More specifically, if the heat of some machine in plant crosses threshold
limit, then automatically switch on the fan (same applied in laptops as well).
UML Structure –
UML structure of state design pattern |
C++ Example –
#include<iostream> using namespace std; class Plant; typedef enum _STATE { ON, OFF } STATE; class IState { public: virtual STATE GetState() = 0; virtual void SwitchOnFan(Plant* machine) { ; } virtual void SwitchOffFan(Plant* machine) { ; } }; class IOnState : public IState { virtual void SwitchOnFan(Plant* machine) = 0; STATE GetState() = 0; }; class OnFan : public IOnState { public: void SwitchOnFan(Plant* machine); inline STATE GetState() { return ON; } }; class IOffState : public IState { virtual void SwitchOffFan(Plant* machine) = 0; inline STATE GetState() = 0; }; class OffFan : public IOffState { void SwitchOffFan(Plant* machine); inline STATE GetState() { return OFF; } }; IState* On(); IState* Off(); class Plant { private: IState *pIState; public: void SwitchOnFan() { if (pIState->GetState() == ON) { cout << "Fan is already running" << endl; } else { On()->SwitchOnFan(this); } } void SwitchOffFan() { if (pIState->GetState() == OFF) { cout << "Fan is already off" << endl; } else { Off()->SwitchOffFan(this); } } void SetState(IState* state) { pIState = state; } explicit Plant() { pIState = Off(); } }; void OnFan::SwitchOnFan(Plant* machine) { cout << "Switching on fan" << endl; machine->SetState(this); } void OffFan::SwitchOffFan(Plant* machine) { cout << "Switching Off fan" << endl; machine->SetState(this); } namespace { IState* pOn = new OnFan(); IState* pOff = new OffFan(); } IState* On() { return pOn; } IState* Off() { return pOff; } void Setup() { On(); Off(); } /* ####################################################################################### ######### __ __ _ ###################################################### ######### | \/ | __ _(_)_ __ ###################################################### ######### | |\/| |/ _` | | '_ \ ###################################################### ######### | | | | (_| | | | | | ###################################################### ######### |_| |_|\__,_|_|_| |_| ###################################################### ####################################################################################### */ int main(int argc, char** argv) { Setup(); Plant objPlant; int temperature; do { cout<<endl<<"Enter the temperature in degree Celsius - "; cin>>temperature; if(temperature > 40) objPlant.SwitchOnFan(); else objPlant.SwitchOffFan(); cout<<endl<<"enter -1 for exit any other number to continue - "; cin>>temperature; } while(temperature != -1); return 0; } ///////////////////////////////////////////////////////////////////-------------OutPut /* Enter the temperature in degree Celsius - 25 Fan is already off enter -1 for exit any other number to continue - 1 Enter the temperature in degree Celsius - 45 Switching on fan enter -1 for exit any other number to continue - 1 Enter the temperature in degree Celsius - 25 Switching Off fan */
Thanks for reading it. To learn more about design patterns and basic design principles, please see my web page.
Comments
Post a Comment