Hello
Example inspired by Russian language explanation of
[DesignPattern] Command
Native Russian speakers can see the good video on YouTube
So, the example is about remote control for home stuff, like TV, MP3 player, Lights etc. We assume, that these devices have only on/off states
So, lets design RC for Light, MP3 and video electrical devices
RC will be looks like box with 6 buttons:
- Light on
- Light off
- MP3 on
- MP3 off
- Video on
- Video off
Lets code it!
namespace RemoteControlDP
{
public class RemoteControl
{
public void DrawMenu()
{
Console.WriteLine("Select operation:");
Console.WriteLine("1\t on light");
Console.WriteLine("1 off\t off light");
Console.WriteLine("2\t on tv");
Console.WriteLine("2 off\t off tv");
Console.WriteLine("3\t on mp3");
Console.WriteLine("3 off\t off mp3");
}
public enum ProductState { On, Off }
public void PerformAction()
{
string userSelected = Console.ReadLine();
switch (userSelected)
{
case "1":
LightOn();
break;
case "1 off":
LightOff();
break;
case "2":
TVOn();
break;
case "2 off":
TVOff();
break;
case "3":
MP3On();
break;
case "3 off":
MP3Off();
break;
}
}
private static void MP3Off()
{
Console.WriteLine("MP3 is off");
}
private static void MP3On()
{
Console.WriteLine("MP3 is on");
}
private static void TVOff()
{
Console.WriteLine("TV is off");
}
private static void TVOn()
{
Console.WriteLine("TV is on");
}
private static void LightOff()
{
Console.WriteLine("Light is off");
}
private static void LightOn()
{
Console.WriteLine("Light is on");
}
}
}
Client code:
namespace Client
{
class Program
{
static void Main(string[] args)
{
RemoteControl remote = new RemoteControl();
string userInput = string.Empty;
do
{
remote.DrawMenu();
remote.PerformAction();
Console.WriteLine("To continue select y");
userInput = Console.ReadLine();
}
while (userInput.Equals("y"));
}
}
}
UML class Diagram lol:
Very good, until comes one of 3 inevitable things: Change Requirements
Client wish command on Fan!
So, lets rock. Class RemoteControl gonna be opened, changed, added etc. Bad smell!
After client wish add Radio, printer, PC etc.
Lets encapsulate changes and use some smart thing, was invented by 4 smart humans. Lets refactor to Command Design Pattern : "Encapsulate a request as an object, thereby letting you parameterize clients with different requests, queue or log requests, and support undoable operations".
Big picture: i wish create structure of classes and its relationships, so i can create programs and assign to buttons in real-time. Looks fine! It is like programmable Logitech remote control:
which can be programmed in real time and commands will be assigned to buttons! A little difference, SW we do in our self, no need to pay for it :-)
First part: create commands which do On operation (Off operation will be described in second part):
ICommand interface. There only one method:
namespace RemoteControlDP
{
public interface ICommand
{
void Execute();
}
}
Light, MP3 and Video implementations. Each implementation also have overriding of ToString. We will see use of it in RemoteControl class.
using System;
namespace RemoteControlDP
{
public class LightCommand : ICommand
{
public void Execute()
{
Console.WriteLine("Light is on");
}
public override string ToString()
{
return "on Light";
}
}
}
using System;
namespace RemoteControlDP
{
public class MP3Command : ICommand
{
public void Execute()
{
Console.WriteLine("MP3 is on");
}
public override string ToString()
{
return "on MP3";
}
}
}
using System;
namespace RemoteControlDP
{
public class TVCommand : ICommand
{
public void Execute()
{
Console.WriteLine("TV is on");
}
public override string ToString()
{
return "on TV";
}
}
}
RemoteControl refactored:
using System;
using System.Collections.Generic;
namespace RemoteControlDP
{
public class RemoteControl
{
Dictionary<string, ICommand> _commands;
public RemoteControl()
{
_commands = new Dictionary<string, ICommand>();
}
public void SetCommand(string button, ICommand cmd)
{
_commands[button] = cmd;
}
public void DrawMenu()
{
Console.WriteLine("Select operation:");
foreach (String btn in _commands.Keys)
{
Console.WriteLine("{0} \t - {1}", btn, _commands[btn].ToString());
}
}
public void PerformAction()
{
string userSelected = Console.ReadLine() ?? String.Empty;
if (_commands.ContainsKey(userSelected))
{
_commands[userSelected].Execute();
}
}
}
}
RemoteControl explained:
- Collection. There collection of assigned commands per buttons. Class have no idea about command. It is incapsulated.
- SetCommand. Ability assign buttons to commands from outside. From client, for a true.
- DrawMenu. There no need long list of strings, only calling to overridden ToString method in ICommand implementations.
- PerformAction. Just call to Execute method of ICommand implementations.
Client slightly changed:
namespace Client
{
class Program
{
static void Main(string[] args)
{
RemoteControl remote = new RemoteControl();
string userInput = string.Empty;
remote.SetCommand("1", new LightCommand());
remote.SetCommand("2", new TVCommand());
remote.SetCommand("3", new MP3Command());
do
{
remote.DrawMenu();
remote.PerformAction();
Console.WriteLine("To continue select y");
userInput = Console.ReadLine();
}
while (userInput.Equals("y"));
}
}
}
There using of assign command to buttons in real-time.
So, if we need add Fan, we only create Fan class, implement ICommand and add it to remote, using SetCommand.
UML class diagram:
UML Sequence diagram:
Client - is same as our client
Invoker - is RemoteControl
Command - is ICommand
ConcreteCommand - is Light/MP3/TV command
Receiver - will be added later
Lets continue refactoring to GOF pattern.
I wish add Receiver class. Is real implementation of devices. Its includes its state too.
My recievers called LightReceiver, MP3Receiver, TVReceiver.
namespace RemoteControlDP
{
public enum ReceiverState { On, Off, }
}
using System;
namespace RemoteControlDP
{
public class TVReceiver
{
public void TurnOn()
{
DeviceState = ReceiverState.On;
Console.WriteLine("TV is {0}", DeviceState);
}
public ReceiverState DeviceState { get; private set; }
}
}
using System;
namespace RemoteControlDP
{
public class MP3Receiver
{
public void TurnOn()
{
DeviceState = ReceiverState.On;
Console.WriteLine("MP3 is {0}", DeviceState);
}
public ReceiverState DeviceState { get; private set; }
}
}
See 2 kinds of lights: Color and somple light:
using System;
namespace RemoteControlDP
{
public abstract class LightReceiver
{
public virtual void TurnOn()
{
DeviceState = ReceiverState.On;
Console.WriteLine("Light is {0}", DeviceState);
}
public ReceiverState DeviceState { get; private set; }
}
public class ColorLightReceiver : LightReceiver
{
public override void TurnOn()
{
base.TurnOn();
Console.WriteLine("Red Color");
}
}
}
using System;
namespace RemoteControlDP
{
public class FanReceiver
{
public void TurnOn()
{
DeviceState = ReceiverState.On;
Console.WriteLine("Fan is {0}", DeviceState);
}
public ReceiverState DeviceState { get; private set; }
}
}
Commands changed too:
This class will contain injected Recievers realisation and have method Action
Receiver for Light can be injected as different Lights implementations, so it makes system more flexible.
using System;
namespace RemoteControlDP
{
public class FanCommand : ICommand
{
private FanReceiver _fan;
public FanCommand(FanReceiver fan)
{
_fan = fan;
}
public void Execute()
{
_fan.TurnOn();
}
public override string ToString()
{
return "on Fan";
}
}
}
using System;
namespace RemoteControlDP
{
public class LightCommand : ICommand
{
private LightReceiver _light;
public LightCommand(LightReceiver light)
{
_light = light;
}
public void Execute()
{
_light.TurnOn();
}
public override string ToString()
{
return "on Light";
}
}
}
using System;
namespace RemoteControlDP
{
public class MP3Command : ICommand
{
private MP3Receiver _mp3;
public MP3Command(MP3Receiver mp3)
{
_mp3 = mp3;
}
public void Execute()
{
_mp3.TurnOn();
}
public override string ToString()
{
return "on MP3";
}
}
}
using System;
namespace RemoteControlDP
{
public class TVCommand : ICommand
{
private TVReceiver _tv;
public TVCommand(TVReceiver tv)
{
_tv = tv;
}
public void Execute()
{
_tv.TurnOn();
}
public override string ToString()
{
return "on TV";
}
}
}
Client changed too:
namespace Client
{
class Program
{
static void Main(string[] args)
{
RemoteControl remote = new RemoteControl();
string userInput = string.Empty;
remote.SetCommand("1", new LightCommand(new ColorLightReceiver()));
remote.SetCommand("2", new TVCommand(new TVReceiver()));
remote.SetCommand("3", new MP3Command(new MP3Receiver()));
remote.SetCommand("4", new FanCommand(new FanReceiver()));
do
{
remote.DrawMenu();
remote.PerformAction();
Console.WriteLine("To continue select y");
userInput = Console.ReadLine();
}
while (userInput.Equals("y"));
}
}
}
You can see, Light command injected by Color light. It can be injected by other implementation of light.
Now, our design is looks like GOF design.
Class diagram:
We can stop here, but Command design pattern by GOF "...and support undoable operations."
Later i will cover it, or try it yourself.
that's it