By Sihui Huang
The Command Pattern’s definition is a stressful one to look at. The formal definition is that it:
- 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, writes 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.
a. We selected the services we wanted from the menu and submitted them to a concierge.
b. The concierge wrote these service requests down as a list.
c. After we hung up, instructed by the service menu, the concierge sent our requests to corresponding departments.
d. Each department executed on the given request.
Let’s see the actions in Ruby.
We submitted these three requests to the concierge:
These requests went into a list the concierge keeps track of:
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
.
- Instructed by the service menu, the concierge sent our requests to corresponding departments.
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.
- We want to offer new services or remove an existing service. However, each time 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. Each time you update a piece of code is an opportunity to introduce new bugs to the system.
When the method also knows a ton — and 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 each of them accordingly.
But what if each request actually knew how to execute itself?
Then the method can be as simple as:
Instead of letting the act_on_requests
method decide 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:
And the updated Concierge
will look like:
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:
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
:
And here is how we can create a service panel:
??We are now using the Command Pattern!??
Let’s revisit the definition of the Command Pattern. It:
- encapsulates a request as an object
- thereby letting you parameterize other objects with different requests, queue or log requests, and support undoable operations.
- “encapsulates a request as an object”
Each of the services classes we created, RoomService
, LaundryService
, TripPlanningService
, and SpaReservationService
, is an example of encapsulating a request as an object.
Recap:
- “thereby letting you parameterize other objects with different requests,”
The ServicePanel
is an example of parameterizing an object with different requests.
Recap:
- “queue or log requests,”
Our requests were queued while the concierge was taking them over the phone.
Recap:
- and support undoable operations.
SpaReservationService
supports undo
.
Recap:
Thanks for reading!
Don’t forget to subscribe. :D
This was originally published on my blog, Design patterns in life and Ruby.