In OOPS, Facade is a structural design pattern that simplifies the interaction between the client and complex subsystem. For this, we design a facade class that works as an interface between the client and interdependent classes in the subsystem. In other words, the facade is a wrapper class, which encapsulates the complexity of the subsystem and provides a simple interface to the client. So the client will interact only with the facade object, which interacts with the subsystem objects to perform the requested operations.
Suppose we are at a restaurant and want to order a meal. If we go directly to the kitchen, we need to communicate with various chefs. This can be a complicated process. One simple solution would be to interact with a simplified interface i.e. Waiter. Here waiter will act as a facade for the kitchen system. So we will tell the waiter what we want, and waiter will communicate order to the kitchen. Now kitchen will prepare the meal and sends it through waiter.
In simple words: We don’t need to worry about the complexity of the kitchen, such as which chef to talk to, how to communicate the order, or how the meal is prepared. We only need to interact with the waiter.
Suppose we have a system that manages various operations related to a car like starting the engine, changing gears, and turning the car. This system consists of multiple interfaces, classes, and methods, which can be complicated for a client to use directly.
interface Engine {
void start();
void stop();
}
interface Gearbox {
void shiftUp();
void shiftDown();
}
interface Steering {
void turnLeft();
void turnRight();
}
To solve this problem, we will create a Facade interface i.e. a unified interface for the above set of interfaces. In other words, we will wrap the car subsystem with a simpler CarFacade interface. Here is an example:
interface CarFacade {
void start();
void stop();
void shiftUp();
void shiftDown();
void turnLeft();
void turnRight();
}
This interface will be more accessible and easier for a client to use. Now we will define a concrete implementation CarFacadeImpl class that implements the CarFacade interface, which internally uses the car subsystem to perform the required tasks.
Java code implementation of CarFacadeImpl
class CarFacadeImpl implements CarFacade {
private Engine engine;
private Gearbox gearbox;
private Steering steering;
public CarFacadeImpl() {
engine = new EngineImpl();
gearbox = new GearboxImpl();
steering = new SteeringImpl();
}
public void start() {
engine.start();
}
public void stop() {
engine.stop();
}
public void shiftUp() {
gearbox.shiftUp();
}
public void shiftDown() {
gearbox.shiftDown();
}
public void turnLeft() {
steering.turnLeft();
}
public void turnRight() {
steering.turnRight();
}
}
Here CarFacadeImpl has references to the Engine, Gearbox, and Steering interfaces, which uses them to implement the methods of the CarFacade interface. Now the client will use the CarFacade interface to perform various car operations, without worrying about the subsystem complexity. Here’s an example of how a client can use the CarFacade:
public class Client {
public static void main(String[] args) {
CarFacade car = new CarFacadeImpl();
car.start();
car.shiftUp();
car.turnLeft();
car.shiftDown();
car.turnRight();
car.stop();
}
}
Facade Pattern decouples client code from the implementation of the subsystem and helps us to modify the subsystem without affecting the client code. But there few drawbacks which we need to take care:
Step 1: Check whether it is possible to provide a simpler interface than what the existing subsystem already provides to make the client code independent from many subsystem’s classes. If yes, then move forward to design a unified Facade interface for the subsystem.
Step 2: We need to design a Facade class that implements the Facade interface and encapsulates the subsystem. This class should capture the collaborations and functionality of the subsystem and redirect the calls from the client code to the appropriate objects of the subsystem.
Step 3: If the Facade becomes too big, we can think to extract part of its behavior to additional Facade classes. Designing additional Facades can help in providing flexibility and handling various types of clients.
Step 4: Clients can now use the Facade interface to instantiate the desired Facade object based on their needs. Here client is only coupled to the Facade interface and protected from any changes in the subsystem code. When a subsystem gets upgraded to a new version, you will only need to modify the code in the facade.
Overall, facade pattern is a trade-off between simplification and restriction. Over-simplifying a system can mean that the user is over-restricted, leading to less freedom than necessary. Under-simplifying the Facade pattern can result in too much freedom, making the Facade pattern irrelevant. Finding the right balance is what makes a good, useful, and effective Facade.
Suppose we have a music player system that has various components like an audio player, equalizer, playlist manager, and visualizer. Here each component has a set of methods that can be used to control its behaviour.
Without using the Facade pattern, the client should know the details of each of these components to use them. On another side, by using the Facade pattern, we can provide a simple interface that encapsulates the complexity of the music player system.
interface MusicPlayerFacade {
void play();
void pause();
void stop();
void nextTrack();
void prevTrack();
void setVolume(int volume);
}
class MusicPlayerFacadeImpl implements MusicPlayerFacade {
private AudioPlayer audioPlayer;
private Equalizer equalizer;
private PlaylistManager playlistManager;
private Visualizer visualizer;
public MusicPlayerFacadeImpl() {
audioPlayer = new AudioPlayer();
equalizer = new Equalizer();
playlistManager = new PlaylistManager();
visualizer = new Visualizer();
}
public void play() {
audioPlayer.play();
visualizer.showVisuals();
}
public void pause() {
audioPlayer.pause();
visualizer.hideVisuals();
}
public void stop() {
audioPlayer.stop();
visualizer.hideVisuals();
}
public void nextTrack() {
audioPlayer.playNextTrack();
visualizer.showVisuals();
playlistManager.nextTrack();
}
public void prevTrack() {
audioPlayer.playPrevTrack();
visualizer.showVisuals();
playlistManager.prevTrack();
}
public void setVolume(int volume) {
audioPlayer.setVolume(volume);
}
}
In the above example, the MusicPlayerFacade interface defines a simple set of methods that clients can use. The MusicPlayerFacadeImpl class implements this interface and provides a simple interface to control the system of the music player.
One important thing to note here: Sometimes Facade object does not provide all the features of the underlying subsystem. For example, advanced users may need to access the equalizer directly in order to fine-tune the audio settings. However, for most users, the simple interface provided by the Facade is sufficient.
Mediator and Facade patterns have similarities: both abstract the functionality of existing classes and aim to organize collaboration between tightly coupled classes. There are few differences:
Suppose we want to integrate our code with a library that contains various objects. For this, we need to initialize all these objects, track their dependencies, and ensure that methods are executed in the correct order. Due to this, our code becomes tightly coupled with the implementation details of the third-party classes, which makes it challenging to understand and maintain. What should we do to solve this problem?
One approach is to use the facade class, which acts as a mediator between the code and the library, hide the implementation details and provide an interface that the code can use. In other words, we can create a new class that exposes a simplified API for the objects and methods of the library.
This class will be responsible for initializing and managing the library objects, tracking their dependencies, and ensuring that methods are executed in the correct order. Now, if we ever need to switch to a different library, we only need to update the implementation of the facade class, rather than our entire codebase.
We highly recommend to explore some other code examples and applications of the facade pattern. If you have any queries/doubts/feedback, please write us at contact@enjoyalgorithms.com. Enjoy learning, Enjoy OOPS, Enjoy algorithms!