In this article, we will do in-depth exploration of various aspects of Facade Design pattern in Java.
What is Facade Design Pattern in Java ?
Facade design pattern in Java provides simplified interface to a complex system with multiple sub-sytems. The Facade interacts with the subsystems instead of the Client directly interacting with subsystems.
Complex System Without Facade
Complex System with Facade
Example: Facade Design Pattern in Java
We will take up an example of a TravelBooking System wherein the Traveler can look for available Buses or Trains for a journey from one city to another on a particular date.The traveler will request the TravelFacade to check for the available Buses/Trains. TravelFacade will internally check with the Subsystems and get the details for the Traveler.
Let us look into the Class Diagram and the Java code.
Class Diagram
Java Code
Bus Class
package com.design.facade;
public class Bus {
private String busNumber;
private String fromCity;
private String toCity;
private String startTimeDate;
private String endTimeDate;
public String getBusNumber() {
return busNumber;
}
public void setBusNumber(String busNumber) {
this.busNumber = busNumber;
}
public String getFromCity() {
return fromCity;
}
public void setFromCity(String fromCity) {
this.fromCity = fromCity;
}
public String getToCity() {
return toCity;
}
public void setToCity(String toCity) {
this.toCity = toCity;
}
public String getStartTimeDate() {
return startTimeDate;
}
public void setStartTimeDate(String startTimeDate) {
this.startTimeDate = startTimeDate;
}
public String getEndTimeDate() {
return endTimeDate;
}
public void setEndTimeDate(String endTimeDate) {
this.endTimeDate = endTimeDate;
}
}
Train Class
package com.design.facade;
public class Train {
private String busNumber;
private String fromCity;
private String toCity;
private String startTimeDate;
private String endTimeDate;
public String getBusNumber() {
return busNumber;
}
public void setBusNumber(String busNumber) {
this.busNumber = busNumber;
}
public String getFromCity() {
return fromCity;
}
public void setFromCity(String fromCity) {
this.fromCity = fromCity;
}
public String getToCity() {
return toCity;
}
public void setToCity(String toCity) {
this.toCity = toCity;
}
public String getStartTimeDate() {
return startTimeDate;
}
public void setStartTimeDate(String startTimeDate) {
this.startTimeDate = startTimeDate;
}
public String getEndTimeDate() {
return endTimeDate;
}
public void setEndTimeDate(String endTimeDate) {
this.endTimeDate = endTimeDate;
}
}
BusSubSystem Class
package com.design.facade;
import java.util.Date;
import java.util.List;
public class BusSubSystem {
public List getBusList(String fromCity, String toCity, Date journeyDate) {
//returns Buses available in that particular journeyDate
return null;
}
}
TrainSubSystem Class
package com.design.facade;
import java.util.Date;
import java.util.List;
public class TrainSubSystem {
public List getBusList(String fromCity, String toCity, Date journeyDate) {
//returns Trains available in that particular journeyDate
return null;
}
}
TravelFacade Class
package com.design.facade;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class TravelFacade {
private BusSubSystem busSubSytem;
private TrainSubSystem trainSubSystem;
public List getBusList(String fromCity, String toCity, Date journeyDate) {
return busSubSytem.getBusList(fromCity, toCity, journeyDate);
}
public TravelFacade() {
busSubSytem = new BusSubSystem();
trainSubSystem = new TrainSubSystem();
}
public List getTrainList(String fromCity, String toCity, Date journeyDate) {
return trainSubSystem.getBusList(fromCity, toCity, journeyDate);
}
public Map < String, Object > getBusesAndTrains(String fromCity, String toCity, Date journeyDate) {
Map < String, Object > map = new HashMap < > ();
map.put("Buses", getBusList(fromCity, toCity, journeyDate));
map.put("Trains", getTrainList(fromCity, toCity, journeyDate));
return map;
}
}
Traveler Class
package com.design.facade;
import java.util.Date;
public class Traveler {
public static void main(String[] args) {
TravelFacade travelFacade = new TravelFacade();
travelFacade.getBusesAndTrains("Delhi", "Gurgaon", new Date());
}
}
When should we use Facade Design Pattern in Java?
The Facade design pattern in Java should be used when you want to provide a simplified and unified interface to a complex subsystem or set of classes. It acts as a higher-level interface that hides the underlying complexities and interactions of multiple components, making it easier for clients to interact with the system.
You should consider using the Facade design pattern in Java under the following circumstances:
- Complex Subsystem: When your system consists of multiple classes or components with complex interactions and dependencies, using the Facade pattern can help simplify the interactions and present a cleaner interface to clients.
- Simplifying Client Code: If the client code needs to interact with several classes and perform multiple steps to achieve a specific task, a Facade can consolidate these steps into a single method call, making the client code more readable and intuitive.
- Subsystems with Changing Interfaces: When the subsystem’s classes or interfaces are subject to change or evolution, a Facade can act as a stable entry point for clients. This shields clients from the impact of changes in the underlying components.
- Improving Code Maintainability: If the client code is tightly coupled with multiple classes and becomes difficult to maintain, a Facade can decouple the client from the intricate details of the subsystem, making the codebase more maintainable.
- Reducing Dependencies: Facade helps reduce the direct dependencies between clients and subsystems. This isolation allows for better management of changes in the subsystem without affecting client code.
- Integration of Legacy Systems: When integrating legacy systems with new components or services, a Facade can serve as an intermediary that communicates with the legacy code and presents a unified interface to the modern components.
Pros of Facade Design Pattern in Java
The Facade design pattern in Java offers several advantages that make it a valuable tool for simplifying complex systems and improving the interaction between clients and subsystems. Here are some key pros of using the Facade design pattern:
- Simplified Interface: Facade provides a simple and unified interface that hides the complexities of the underlying subsystem. This makes it easier for clients to interact with the system without needing to understand its intricate details.
- Abstraction of Complexity: Facade abstracts away the complex interactions and dependencies among various subsystem components. Clients only need to interact with the facade’s methods, shielding them from the inner workings of the system.
- Reduced Coupling: Clients are decoupled from the details of individual subsystem components. This reduces the dependencies between clients and subsystems, making the codebase more modular and maintainable.
- Code Reusability: By providing a common interface to access subsystem features, the facade promotes code reuse. Clients can use the same facade to access different parts of the subsystem without duplicating code.
- Improved Readability: Facade simplifies client code by encapsulating multiple steps and interactions within a single method call. This leads to more readable and understandable client code.
- Easier Maintenance: Changes made to the subsystem or its components don’t affect clients using the facade. This isolation ensures that clients remain unaffected by changes to the internal details of the system.
- Integration of Legacy Systems: When integrating legacy systems with modern components, the facade can serve as a bridge, allowing the legacy code to interact with new subsystems through a common interface.
- Flexibility: Despite simplifying the client’s interaction with the subsystem, the facade can still offer flexibility by exposing multiple methods that provide different levels of functionality.
- Encapsulation: The facade encapsulates the complexity of the subsystem within its methods, providing a clear boundary between the client code and the subsystem. This encapsulation aids in maintaining a clean and organized codebase.
- Adaptation and Abstraction: The facade can adapt or abstract away certain functionalities, such as dealing with different data formats or APIs. This allows clients to work with a consistent interface regardless of the specifics of the subsystem.
Cons of Facade Design Pattern in Java
While the Facade design pattern in Java offers various advantages, it also comes with certain limitations and considerations that developers should be aware of. Here are some key cons of using the Facade design pattern:
- Limited Customization: The simplicity provided by the facade pattern might limit the ability to customize interactions with subsystem components. Clients may require specific configurations that the facade does not expose.
- Tight Coupling to Facade: While the facade reduces coupling between clients and subsystems, it can introduce tight coupling between clients and the facade itself. Changes to the facade might impact multiple clients.
- Potential for Overuse: Applying the facade pattern to every interaction with subsystems can lead to overuse and unnecessary abstraction. It’s important to assess whether the complexity of the subsystem justifies the use of a facade.
- Less Control: Some clients might require fine-grained control over subsystem components. The facade, by abstracting details, might restrict clients from directly interacting with these components.
- Additional Layer: The introduction of the facade adds an extra layer of abstraction. While this simplifies client code, it might add a level of indirection that could affect performance in certain scenarios.
- Maintenance Overhead: Changes made to the subsystem might necessitate corresponding changes in the facade. If the facade isn’t designed well, these changes could lead to maintenance challenges.
- Design Complexity: Creating a facade that strikes the right balance between providing a simplified interface and exposing necessary functionalities can be challenging. Poorly designed facades can introduce their own complexities.
- Loss of Granularity: The facade abstracts away the internal details of subsystem components. This might result in a loss of granularity and transparency, making it harder to debug or troubleshoot issues.
Relationship of Facade Design Pattern with other Design Patterns
The Facade design pattern in Java has relationships with several other design patterns due to its role in simplifying interactions between clients and complex subsystems. Here are some of the key relationships the Facade pattern has with other design patterns:
- Adapter Pattern: The Adapter pattern is often used in conjunction with the Facade design pattern in Java. While the Facade pattern provides a higher-level interface to a subsystem, the Adapter pattern is used to convert the interface of a class into another interface that clients expect. Adapters can be employed within a Facade to make the subsystem’s interfaces compatible with the simplified facade interface.
- Mediator Pattern: The Mediator pattern focuses on providing a centralized mediator object that controls the communication between multiple objects. The Mediator Pattern and Facade Pattern can be used together to achieve more structured and efficient communication within a complex system while also providing a simplified interface to clients.
- Decorator Pattern: The Decorator pattern and the Facade pattern both aim to simplify interactions, but they do so in different ways. The Decorator pattern enhances the behavior of individual objects dynamically by adding responsibilities, whereas the Facade pattern provides a simplified interface to an entire subsystem. These patterns can be used together to provide a simplified facade interface that is further enhanced with decorators.
- Composite Pattern: The Composite pattern is about composing objects into tree structures to represent part-whole hierarchies. While it focuses on treating individual objects and compositions uniformly, the Facade pattern is concerned with simplifying the interactions with a complex subsystem. The two patterns can be combined to provide a simplified way to work with composite structures.
- Proxy Pattern: Proxies and facades both control access to objects, but they have different goals. Proxies provide an intermediary for controlling access to an object, adding lazy initialization, or performing access control. The Facade pattern simplifies interactions with a subsystem by providing a higher-level interface. Depending on the situation, a Facade can use proxies to control access to components of the subsystem.
- Singleton Pattern: Facades can sometimes make use of the Singleton pattern to ensure that there is only one instance of the facade created. This ensures that clients always interact with the same facade instance and helps manage the subsystem’s interactions consistently.
Conclusion: Facade Design Pattern in Java
In this comprehensive guide on the Facade Design Pattern in Java, we delved into various aspects of this structural design pattern.
The Facade pattern offers a simplified interface to a complex subsystem, streamlining interactions between clients and the subsystem’s components. We explored its definition, purpose, and its real-life analogy.
Through an illustrative example of a TravelBooking System, we saw how the Facade pattern simplifies interactions with subsystems and enhances code readability.
The class diagram and Java code for the example clarified the roles of different classes within the Facade pattern implementation. The Bus, Train, BusSubSystem, TrainSubSystem, TravelFacade, and Traveler classes collectively demonstrated how to use the Facade pattern to provide a unified interface for accessing travel options.
Furthermore, we examined when to use the Facade pattern—specifically, in scenarios involving complex subsystems, the need for simplified client code, evolving interfaces, enhanced code maintainability, and legacy system integration. The pros of the Facade pattern highlighted its ability to provide a simplified interface, abstract complexity, reduce coupling, improve code maintainability, and aid in legacy system integration.
On the flip side, we discussed the potential cons, such as limited customization, tight coupling to the facade, potential for overuse, and design complexity.
We also explored the relationships between the Facade pattern and other design patterns, emphasizing how it works in harmony with patterns like Adapter, Mediator, Decorator, Composite, Proxy, and Singleton.
In conclusion, the Facade Design Pattern in Java serves as a powerful tool to simplify complex systems and enhance interactions between clients and subsystems.
Must Read Articles:
- Design Patterns in Java: For Efficient Development 2023
- Professional Tips: Top 10 Design Principles in Java 2023