If youâve ever had to call three services in a specific order just to book a hotel room, and gotten it wrong once because you forgot to check availability before reserving, this is for you. Thatâs the exact shape of the hotel example: RoomBookingService, HousekeepingService, RestaurantService, each fine on its own, miserable to coordinate correctly from outside.
The problem
A real operation touches multiple subsystems in a specific sequence, and every caller who wants to perform that operation has to know the sequence, the dependencies between the calls, and the error handling for each step. Duplicate that knowledge across enough call sites and a change to the sequence means hunting down every place that got it right (or wrong) independently.
Without the pattern
Drop the facade and the caller is left holding all three subsystem objects directly: a RoomBookingService, a HousekeepingService, a RestaurantService, plus whichever Menu implementation it picked. Booking a room means the caller has to know, on its own, that checkAvailability comes before reserveRoom, that reserveRoom comes before cleanRoom, and that prepareRoom only makes sense once the roomâs actually been cleaned, none of which is enforced anywhere except in whatever order the caller happens to type the calls. Ordering food means picking a Menu implementation, calling getMenu(), then remembering orderFood has to run before deliverFood, and that neither one means anything until a roomâs been booked. Write that sequence once in a checkout controller and once in an admin rebooking tool and youâve got two independent chances for someone to swap two lines and never notice, because nothing here throws if you clean a room before youâve reserved it, it just quietly prepares a room nobodyâs checked into.
sequenceDiagram
participant Caller
participant RoomBookingService
participant HousekeepingService
participant RestaurantService
Caller->>RoomBookingService: checkAvailability(roomNumber)
RoomBookingService-->>Caller: true
Caller->>RoomBookingService: reserveRoom(roomNumber)
Caller->>HousekeepingService: cleanRoom(roomNumber)
Caller->>HousekeepingService: prepareRoom(roomNumber)
Note over Caller,RestaurantService: Caller still has to separately track down a Menu,<br/>call orderFood, then deliverFood, in that order,<br/>every single time it wants food sent up
Caller->>RestaurantService: orderFood(foodItem)
Caller->>RestaurantService: deliverFood(roomNumber, foodItem)
Nothing here is wrong on any individual call, RoomBookingService and HousekeepingService and RestaurantService all do exactly what they say. The problem is that the correct ordering and the dependency between the calls exists only in whichever caller wrote this sequence out by hand, and it has to be re-derived correctly at every call site that wants to book a room or send up food.
With the pattern
HotelKeeper is the facade. Its constructor creates and holds a HousekeepingService, a RestaurantService, and a RoomBookingService, all owned internally, the caller never sees them.
bookRoom(int roomNumber) is one method that does the whole sequence: call roomBooking.checkAvailability(roomNumber), and only if that returns true, call roomBooking.reserveRoom(roomNumber), then housekeeping.cleanRoom(roomNumber), then housekeeping.prepareRoom(roomNumber). The caller gets one method call. The ordering logic and the branch on availability live in exactly one place.
orderRoomService(int roomNumber, String menuType, String foodItem) does the same for the food side: it calls getMenu(menuType), which is itself a small facade over a Menu interface (VegMenu, NonVegMenu, MixedMenu, each just printing their own getMenu()), then calls restaurant.orderFood(foodItem) and restaurant.deliverFood(roomNumber, foodItem). The caller never touches RestaurantService or the Menu implementations directly, HotelKeeper is the only thing that knows they exist.
classDiagram
class HotelKeeper {
-housekeeping: HousekeepingService
-restaurant: RestaurantService
-roomBooking: RoomBookingService
+getMenu(menuType: String) Menu
+bookRoom(roomNumber: int)
+orderRoomService(roomNumber: int, menuType: String, foodItem: String)
}
class HousekeepingService {
+cleanRoom(roomNumber: int)
+prepareRoom(roomNumber: int)
}
class RestaurantService {
+orderFood(menuItem: String)
+deliverFood(roomNumber: int, menuItem: String)
}
class RoomBookingService {
+checkAvailability(roomNumber: int) boolean
+reserveRoom(roomNumber: int)
}
class Menu {
<<interface>>
+getMenu()
}
class VegMenu {
+getMenu()
}
class NonVegMenu {
+getMenu()
}
class MixedMenu {
+getMenu()
}
HotelKeeper o-- HousekeepingService
HotelKeeper o-- RestaurantService
HotelKeeper o-- RoomBookingService
HotelKeeper ..> Menu
Menu <|.. VegMenu
Menu <|.. NonVegMenu
Menu <|.. MixedMenu
What it costs you
HotelKeeper now knows about every subsystem in the hotel, RoomBookingService, HousekeepingService, RestaurantService, all three Menu implementations, and that list only grows if a new subsystem shows up, payments, a concierge desk, whatever comes next. At some point it stops being a thin facade and turns into a hub that everything depends on and that itself depends on everything, which is exactly the coupling this pattern was supposed to get rid of in the first place. It also flattens control on purpose: bookRoom always checks availability, reserves, cleans, and prepares in that fixed order, so a caller that genuinely needs to prepare a room without re-cleaning it, say the roomâs already spotless, has no way to say that through HotelKeeper. It either bypasses the facade and talks to HousekeepingService directly, which puts you right back in the âWithout the patternâ section, or someone bolts a new method or a flag onto HotelKeeper for that one case, and the facade slowly accretes a parameter for every exception anyoneâs ever needed. And when bookRoom throws or does the wrong thing, debugging means stepping through one more layer before you reach the subsystem call that actually failed, a small tax on every call in exchange for not paying it in the coordination logic.
When to reach for it
- A real-world operation needs several subsystem calls in a fixed order, and you donât want that order re-implemented at every call site.
- You want a single, narrow surface (
bookRoom,orderRoomService) that hides which subsystems exist behind it. - The subsystems themselves are fine, the problem is purely that coordinating them from outside is error-prone.
The takeaway
A facade doesnât replace the subsystem classes or simplify what they do, it just moves the coordination logic into one place instead of leaving it implicit in every callerâs head. If two different call sites are calling your subsystem methods in a slightly different order, thatâs the smell that tells you a facade was missing.
Read the full source on GitHub.
Comments