Design Patterns in life and Ruby — gain an intuitive understanding of OO design patterns by linking them with real-life examples.
The Command Pattern’s definition is a stressful one to look at.
The Command Pattern:
- encapsulates a request as an object,
- thereby letting you parameterize other objects with different requests, queue or log requests, and support undoable operations.
Let’s forget about it for a second and take a trip to Hawaii.
And live in a luxury hotel.
We spent the day on the beach, scuba dived, and did some sightseeing. It's time to get back to the hotel to chill, eat, and plan for the next day.
After getting back to the hotel, we want to:
- Get room service for dinner
- Get laundry service because we didn’t bring extra clothes
- Get a travel guide for Kauai, the island we are going to tomorrow
We check out the hotel’s service menu and find three service items matching our needs.
We then call the front desk to place these three requests. A concierge picks up our call, write down our list of requests, and acts on each service request as instructed by the service menu.
Then each staff member executes according to each specific request:
- The chef in the kitchen starts cooking
- The cleaning department send a staff to our room to pick up our clothes
- The staff in the lobby grabs a travel guide and delivers it to our room
Let’s recap what just happened.
- We selected the services we wanted from the menu and submitted them to a concierge.
- The concierge wrote these service requests down as a list.
- After we hung up, instructed by the service menu, the concierge sent our requests to corresponding departments.
- Each department executed on the given request.
Let’s see the actions in Ruby.
1. We submitted these three requests to the concierge:
# a. We submitted our requests one by one to a concierge. we.submit_request_to(concierge, 'dinner_room_service') we.submit_request_to(concierge, 'laundry_service') we.submit_request_to(concierge, 'travel_guide')
2. These requests went into a list the concierge keeps track of:
class Concierge attr_reader :request_list def initialize @request_list = [] end end class We def submit_request_to(concierge, request) concierge.request_list << request end end
Let’s see that in action (console):
As we can see, after we
submitted three requests, these requests were in a request_list
taking care by concierge
.
3. Instructed by the service menu, the concierge sent our requests to corresponding departments.
class Concierge attr_reader :request_list def initialize @request_list = [] end def act_on_requests @request_list.each do |request| case request[:service] when 'room_service' Kitchen.execute(request[:data]) when 'laundry_service' CleaningDepartment.execute(request[:data]) when 'trip_planning_service' TripAdvisor.execute(request[:data]) else raise 'Request Not Supported' end end end
The code above should work fine.
Except for one thing….
It smells bad.
Specifically, the part where we have the switch cases:
Why does this part smell bad?
- If the hotel offers twenty services, instead of three, the method will be really long.
- Each time we want to offer new services or remove an existing service, we have to open the
Concierge
class and redefine theact_on_requests
method.
The method knows too much and requires frequent changes. Having these two combinations together is almost always a bad thing.
Why?
A method that requires frequent changes is a method you need to update often and each time you update a piece of code is an opportunity to introduce new bugs to the system. When the method also knows a tone, a.k.a. it's long, the chances of messing it up when updating increases significantly. That's the reasoning behind a design principle we talked about earlier––encapsulate what varies.
Time to Refactor!
There must be a better way than this:
Take a closer look and think about it.
Let’s rephrase what the code is doing in English. We loop through the requests on the request list. For each request, according to its service type, we give the corresponding department related data and execute the request. Essentially, we loop through the requests and execute on each of them accordingly.
But what if each request actually knows how to execute itself?
Then the method can be as simple as:
Instead of letting the act_on_requests
method decides how each request should be handled, we distribute that responsibility and knowledge back to each request and let it decide how to itself should be handled.
With that being said, our requests could look like this:
class RoomService attr_reader :data, :kitchen def initialize(data) @data = data @kitchen = Kitchen.new end def execute kitchen.cook_for(data) end end class LaundryService attr_reader :data, :cleaning_dpt def initialize(data) @data = data @cleaning_dpt = CleaningDepartment.new end def execute cleaning_dpt.do_laundry_for(data) end end class TripPlanningService attr_reader :data, :tripAdvisor def initialize(data) @data = data @tripAdvisor = TripAdvisor.new end def execute tripAdvisor.plan_for(data) end end
And the updated Concierge
will look like:
class Concierge attr_reader :request_list def initialize @request_list = [] end def act_on_requests @request_list.each do |request| request.execute end end end
With the updated codes, here is how we, customers of the hotel, send requests to the concierge.
It is pretty easy to create another service.
For example, the hotel also allows us to reserve SPA:
class SpaReservationService attr_reader :data, :spa_center def initialize(data) @data = data @spa_center = SpaCenter.new end def execute spa_center.reserve(data) end def undo spa_center.cancel(data) end end
The service not only supports execute
(making a spa reservation) but also undo
(canceling the reservation).
Let’s say the hotel also provides another way to request services without having to call the concierge — a service requesting panel:
We can just press the button and the service with a default setting will be delivered to our room.
Here is the code for the ServicePanel
:
class ServicePanel attr_reader :button_a1, :button_a2, :button_b1, :button_b2 def initialize(a1_service, a2_service, b1_service, b2_service) @button_a1 = Button.new(a1_service) @button_a2 = Button.new(a2_service) @button_b1 = Button.new(b1_service) @button_b2 = Button.new(b2_service) end end class Button attr_reader :service def initialize(service) @service = service end def on_button_click service.execute end end
And here is how we can create a service panel:
??We are using the Command Pattern!??
Let’s revisit the definition of the Command Pattern:
The Command Pattern:
- encapsulates a request as an object,
- thereby letting you parameterize other objects with different requests, queue or log requests, and support undoable operations.
1. “encapsulates a request as an object,”
Each of the services class we created, RoomService
, LaundryService
, TripPlanningService
, and SpaReservationService
, is an example of encapsulating a request as an object.
Recap:
2. “thereby letting you parameterize other objects with different requests,”
The ServicePanel
is an example of parameterizing an object with different requests.
Recap:
3. “queue or log requests,”
Our requests were queued while the concierge was taking them over the phone.
Recap:
4. and support undoable operations.
SpaReservationService
supports undo
.
Recap:
Advantages of The Command Pattern
The Command Pattern lets you:
- parameterize other objects with different requests,
- queue or log requests,
- and support undoable operations.
Yup, the advantages of the command pattern is encapsulated into its definition. ?
Takeaways
The Command Pattern:
- encapsulates a request as an object,
- thereby letting you parameterize other objects with different requests, queue or log requests, and support undoable operations.
Thanks for reading!
Don't forget to subscribe. 😀
Enjoyed the article?
My best content on Software Design, Rails, and Career in Dev. Delivered weekly.
Great explanation. Thank you very much. Is Command Pattern an alternative to Strategy Pattern, or can they sometimes be used together?
Hi Roy,
You are absolutely on the right track trying to understand the difference between Command Pattern and Strategy Pattern. At their cores, they are both examples of using the “Encapsulate what varies” design principle.
Their differences are subtle and more about intention and semantic than structure.
Strategy Pattern is about encapsulating a family of algorithms. Algorithms in the family are different ways of doing the “same thing”. For example, you can have a family of different output algorithms: print to console, print as HTML, and print as XML. In essence, they are all about the same thing: outputting data into a specific format. In this post, I also use different burgers as examples.
When Strategy Pattern enables the program to do the same thing in different ways, Command Pattern allows the program to do different things. In the example used in our post, the different things are taking dinner order, laundry service, and delivering travel guide. In Command Pattern, the common interface for a command object is that it has to respond to
execute
(andundo
if needed). Command Pattern also supports queueing request to execute later and logging requests.In a nutshell, Strategy Pattern is about different ways of doing the same thing while Command Pattern is more about doing different things. They both decouple an object making a request from the one that knows how to perform it.
Let me know if you have any more questions. 🙂
That Dr Phil gif after the definition had my wife and I dying.
lol, I had a great time picking gifs.
This article is amazing! I really dig how you made it easy (and fun!) to understand the Command Pattern.
Thanks, Patrik!
Glad you like it. ?
Your series of articles on design patterns have some of the best explanations that I have ever come across!
Thanks, Trent!
That’s very encouraging to hear! ? ?
I agree with Trent. These are great. Thanks Sihui!
🙂
Nice article. I only strug
gled with rea
ding text wrappi
ng mult
iple li
nes.
Thanks for the feedback and sorry for the bad experience!
What device/screen size are you using?
I am on 15″ laptop @ 1440/900
Hmm interesting, what is the browser you are using?
Firefox 56.0.2 64bit
Here are two examples:
https://ibb.co/hY1fa6
https://ibb.co/mUHa8R
Thanks a lot for the details!
Should be good now 😀
That was awesome! For sure is one of the best articles about Command Pattern even written! Thank you!
Good explanation, I look forward to read more articles from you.
Thanks for the encouragement, Zsolt! It means a lot!
The best explaination is in the comment.