The Façade pattern deals with a subsystem of classes. A subsystem is a set of classes that work in conjunction with each other for the purpose of providing a set of related features. For example, an 1) Account class, 2) Address class and 3) CreditCard class working together, as part of a subsystem, provide features of an online customer.
In real world applications, a subsystem could consist of a large number of classes. Clients of a subsystem may need to interact with a number of subsystem classes for their needs. This kind of direct interaction of clients with subsystem classes leads to a high degree of coupling between the client objects and the subsystem (Figure 3.1). Whenever a subsystem class undergoes a change, such as a change in its interface, all of its dependent client classes may get affected.
When should I use Facade Pattern?
The Façade pattern provides a higher level, simplified interface for a subsystem resulting in reduced complexity and dependency.
This in turn makes the subsystem usage easier and more manageable.
A façade is a class that provides this simplified interface for a subsystem to be used by clients.
With a Façade object in place, clients interact with the Façade object instead of interacting directly with subsystem classes.
The Façade object takes up the responsibility of interacting with the subsystem classes.
In effect, clients interface with the façade to deal with the subsystem. Thus the Façade pattern promotes a weak coupling between a subsystem and its clients
From Figure 3.2, we can see that the Façade object decouples and shields clients from subsystem objects. When a subsystem class undergoes a change, clients do not get affected as before.
Even though clients use the simplified interface provided by the façade, when needed, a client will be able to access subsystem components directly through the lower level interfaces of the subsystem as if the Façade object does not exist.
In this case, they will still have the same dependency/coupling issue as earlier.
Facade Example
Let us build an application that:
Accepts customer details (account, address and credit card details)
Validates the input data
Saves the input data to appropriate data files
Let us say that there are three classes, Account, Address and CreditCard available in the system, each with its own methods for validating and saving the respective data. Let us build a client AccountManager that displays the user interface to a user to input the customer data.
When the client AccountManager is run, it displays the user interface shown using a Swing Application.
In order to validate and save the input data, the client AccountManager would:
Create Account, Address and CreditCard objects
Validate the input data using these objects
Save the input data using these objects
The sequence diagram in Figure 3.4. depicts the message flow between objects.
Applying the Façade pattern in this case can lead to a better design as it promotes low coupling between the client and the subsystem components (Address, Account and CreditCard classes in this case). Applying the Façade pattern, let us define a Façade class CustomerFacade (see the code that follows) that offers a higher level, simplified interface to the subsystem consisting of customer data processing classes (Address, Account and CreditCard).
The CustomerFacade class offers a higher level business service in the form of the saveCustomerData method.
Instead of interacting with each of the subsystem components directly, the client AccountManager can make use of the higher level,
more simplified interface offered by the CustomerFacade object to validate and save the input customer data .
In the revised design, to validate and save the input customer data, the client needs to:
Create or obtain an instance of the façade CustomerFacade class
Send the data to be validated and saved to the CustomerFacade instance
Invoke the saveCustomerData method on the CustomerFacade instance
The CustomerFacade handles the details of creating necessary subsystem objects and calling appropriate methods on those objects to validate and save the customer data. The client is no longer required to directly access any of the subsystem (Account/Address/CreditCard) objects.
Figure 3.7. facade-instance-to-interface shows the message flow in the revised design.
public class Account {
String firstName;
String lastName;
final String ACCOUNT_DATA_FILE = "AccountData.txt";
public Account(String fname, String lname) {
firstName = fname;
lastName = lname;
}
public boolean isValid() {
/* Let's go with simpler validation
here to keep the example simpler.
*/
…
…
}
public boolean save() {
FileUtil futil = new FileUtil();
String dataLine = getLastName() + ”," + getFirstName();
return futil.writeToFile(ACCOUNT_DATA_FILE, dataLine,
true, true);
}
public String getFirstName() {
return firstName;
}
public String getLastName() {
return lastName;
}
}
Address Class
public class Address {
String address;
String city;
String state;
final String ADDRESS_DATA_FILE = "Address.txt";
public Address(String add, String cty, String st) {
address = add;
city = cty;
state = st;
}
public boolean isValid() {
/*
The address validation algorithm
could be complex in real-world applications.
Let us go with simpler validation
here to keep the example simpler.
*/
if (getState().trim().length() < 2)
return false;
return true;
}
public boolean save() {
FileUtil futil = new FileUtil();
String dataLine = getAddress() + ”," + getCity() + ”," +
getState();
return futil.writeToFile(ADDRESS_DATA_FILE, dataLine, true, true);
}
public String getAddress() {
return address;
}
public String getCity() {
return city;
}
public String getState() {
return state;
}
}
CreditCard Class
public class CreditCard {
String cardType;
String cardNumber;
String cardExpDate;
final String CC_DATA_FILE = "CC.txt";
public CreditCard(String ccType, String ccNumber,
String ccExpDate) {
cardType = ccType;
cardNumber = ccNumber;
cardExpDate = ccExpDate;
}
public boolean isValid() {
/*
Let's go with simpler validation
here to keep the example simpler.
*/
if (getCardType().equals(AccountManager.VISA)) {
return (getCardNumber().trim().length() == 16);
}
if(getCardType().equals(AccountManager.DISCOVER)) {
return (getCardNumber().trim().length() == 15);
}
if (getCardType().equals(AccountManager.MASTER)) {
return (getCardNumber().trim().length() == 16);
}
return false;
}
public boolean save() {
FileUtil futil = new FileUtil();
String dataLine =
getCardType() + ,”" + getCardNumber() + ”," +
getCardExpDate();
return futil.writeToFile(CC_DATA_FILE, dataLine, true, true);
}
public String getCardType() {
return cardType;
}
public String getCardNumber() {
return cardNumber;
}
public String getCardExpDate() {
return cardExpDate;
}
}