Facade Design Pattern In Java
Here I am with another article on design patterns — Facade Design Pattern. A Facade object is use to provide a simple interface by hiding complexities of a complex system.
Facade Design Pattern
- The Facade is a Structural Design Pattern and one of the Gang of Four design patterns.
- The Facade object is used to provide a front-facing interface by masking a more complex underlying system.
- The Facade may provide a limited or dedicated set of functionalities. But, the functionalities Facade provides are mainly required by the client application. So, its more caring as per client needs.
- The primary purpose of the Facade is to hide complexities of a system/subsystem by providing simpler interface to deal with.
- Using Facade is super-easy when we have to deal with a complex system/subsystem having lots of functionalities and different configurations.
- So, Facade hides minor and inner details of any third party library, system or subsystem we should know before we deal with it.
- In Java there are many features like JDBC, JPA, JAX-RS etc. which hides the minor details and provide a simpler interface in form of annotations or easier configuration to deal with.
- Even our computer system's POST (power-on-self-test) procedure which runs at the time we start our system; is a good example of Facade. it checks RAM, CPU, HDD and other connected peripherals before giving control over to operating system.
- Facade introduces additional layer of abstraction via Facade. So, if the sub-system changes, we need to do corresponding changes in the facade layer as well.
- We may also have multiple Facade objects one dealing with few subsystems and other dealing with some other subsystems.
I hope we are now clear about what is Facade? To understand it more clearly and the use of Facade in our code, let's take an example of Home Appliance we normally have in our home.
Home Appliance Application using Facade Design Pattern
For easier understanding of usage of Facade, I am here using the sample example application code what used in the Command Design Pattern. I only did some required changes and added the code for additional appliances and functionalities to make example more clear and interesting.
The example also help you to compare Facade with Command Design Pattern.
Code for Appliance class:
xxxxxxxxxx
package org.trishinfotech.facade.devices;
public abstract class Appliance implements Comparable<Appliance> {
protected String name;
protected boolean status;
public Appliance(String name) {
super();
if (name == null || name.trim().isEmpty()) {
new IllegalArgumentException("Appliance name is mandatory for Home Automation");
}
this.name = name;
}
public String name() {
return name;
}
// define operations for appliance
public void on() {
if (status) {
System.out.printf("'%s' is already turned on!\n", name);
} else {
status = true;
System.out.printf("Turning On '%s'\n", name);
}
}
public void off() {
if (!status) {
System.out.printf("'%s' is already turned off!\n", name);
} else {
status = false;
System.out.printf("Turning Off '%s'\n", name);
}
}
// Appliance should be compared only on name.
public int compareTo(Appliance other) {
return this.name.compareToIgnoreCase(other.name);
}
}
I have defined common operations like 'On' and 'Off' here.
Code for Fan class:
xxxxxxxxxx
package org.trishinfotech.facade.devices;
public abstract class Fan extends Appliance {
public static int TOP_SPEED = 4;
public static int LOWEST_SPEED = 1;
protected int currentSpeed = 1;
public Fan(String name) {
super(name);
}
// define operations for fan
public void increase() {
if (currentSpeed < TOP_SPEED) {
currentSpeed++;
System.out.printf("Encreasing Speed of '%s' to '%d'.\n", name, currentSpeed);
} else {
System.out.printf("'%s' is already running at top speed!\n", name);
}
}
public void decrease() {
if (currentSpeed > LOWEST_SPEED) {
currentSpeed--;
System.out.printf("Decreasing Speed of '%s' to '%d'.\n", name, currentSpeed);
} else {
System.out.printf("'%s' is laready running at lowest speed!\n", name);
}
}
}
I have added operations like 'Increase' and 'Decrease' speed here and its a sub-type of Appliance.
Code for Light class:
xxxxxxxxxx
package org.trishinfotech.facade.devices;
public abstract class Light extends Appliance {
public Light(String name) {
super(name);
}
}
No additional operations since we already have 'On' and 'Off' defined.
Code for SoundBar class:
xxxxxxxxxx
package org.trishinfotech.facade.devices;
public abstract class SoundBar extends Appliance {
public static int TOP_VOLUME = 30;
public static int LOWEST_VOLUME = 0;
protected String soundMode;
protected int currentVolume = 1;
protected int volumeWhenMute;
public SoundBar(String name) {
super(name);
}
// define operations for SoundBar
public void setSoundMode(String soundMode) {
this.soundMode = soundMode;
System.out.printf("Setting Sound-Mode of '%s' to '%s'.\n", name, soundMode);
}
public void increaseVolume() {
if (currentVolume < TOP_VOLUME) {
currentVolume++;
System.out.printf("Encreasing volume of '%s' to '%d'.\n", name, currentVolume);
} else {
System.out.printf("'%s' is already on top volume!\n", name);
}
}
public void decreaseVolume() {
if (currentVolume > LOWEST_VOLUME) {
currentVolume--;
System.out.printf("Decreasing volume of '%s' to '%d'.\n", name, currentVolume);
} else {
System.out.printf("'%s' is already on mute!\n", name);
}
}
public void volume(int volume) {
if (volume >= LOWEST_VOLUME && volume <= TOP_VOLUME) {
currentVolume = volume;
System.out.printf("Setting volume of '%s' to '%d'.\n", name, currentVolume);
} else {
System.out.printf("Volume of '%s' is supports range between '%d' and '%d'!\n", name, LOWEST_VOLUME,
TOP_VOLUME);
}
}
public void mute() {
if (currentVolume != LOWEST_VOLUME) {
volumeWhenMute = currentVolume;
currentVolume = 0;
System.out.printf("Putting '%s' on mute!\n", name);
} else {
currentVolume = volumeWhenMute;
System.out.printf("Unmuting '%s'. Setting volume back to '%d'!\n", name, currentVolume);
}
}
public String soundMode() {
return soundMode;
}
}
Here, I have added code for operations like 'Sound-Mode' and 'Increase', 'Decrease' , 'Setting' volume and 'Mute'.
Code for TV class:
xxxxxxxxxx
package org.trishinfotech.facade.devices;
public abstract class TV extends Appliance {
public static int TOP_VOLUME = 30;
public static int LOWEST_VOLUME = 0;
public static int TOP_CHANNEL_NO = 999;
public static int LOWEST_CHANNEL_NO = 1;
protected int currentVolume = 1;
protected int currentChannel = 1;
protected int volumeWhenMute;
public TV(String name) {
super(name);
}
// define operations for TV
public void increaseVolume() {
if (currentVolume < TOP_VOLUME) {
currentVolume++;
System.out.printf("Encreasing volume of '%s' to '%d'.\n", name, currentVolume);
} else {
System.out.printf("'%s' is already on top volume!\n", name);
}
}
public void decreaseVolume() {
if (currentVolume > LOWEST_VOLUME) {
currentVolume--;
System.out.printf("Decreasing volume of '%s' to '%d'.\n", name, currentVolume);
} else {
System.out.printf("'%s' is already on mute!\n", name);
}
}
public void mute() {
if (currentVolume != LOWEST_VOLUME) {
volumeWhenMute = currentVolume;
currentVolume = 0;
System.out.printf("Putting '%s' on mute!\n", name);
} else {
currentVolume = volumeWhenMute;
System.out.printf("Unmuting '%s'. Setting volume back to '%d'!\n", name, currentVolume);
}
}
public void increaseChannel() {
if (currentChannel < TOP_CHANNEL_NO) {
currentChannel++;
System.out.printf("Encreasing channel of '%s' to '%d'.\n", name, currentChannel);
} else {
System.out.printf("'%s' is already showing channel '%d'!\n", name, currentChannel);
}
}
public void decreaseChannel() {
if (currentChannel > LOWEST_CHANNEL_NO) {
currentChannel--;
System.out.printf("Decreasing channel of '%s' to '%d'.\n", name, currentChannel);
} else {
System.out.printf("'%s' is already showing channel '%d'!\n", name, currentChannel);
}
}
}
I have added operations like 'Increase/Decrease Volume', 'Increase/Decrease Channel' and 'Sound Mute'.
Code for CoffeeMaker class:
xxxxxxxxxx
package org.trishinfotech.facade.devices.kitchen;
import org.trishinfotech.facade.devices.Appliance;
public class CoffeeMaker extends Appliance {
public CoffeeMaker() {
super("CoffeeMaker");
}
}
Code for ElectricGrill class:
xxxxxxxxxx
package org.trishinfotech.facade.devices.kitchen;
import org.trishinfotech.facade.devices.Appliance;
public class ElectricGrill extends Appliance {
protected int temp;
public ElectricGrill() {
super("ElectricGrill");
}
public void setTemp(int temp) {
this.temp = temp;
System.out.printf("Setting '%s' temprature to '%d'.\n", name, temp);
}
public int temperature() {
return temp;
}
}
Here, I have added operation to set 'Temperature'.
Code for KitchenLight class:
xxxxxxxxxx
package org.trishinfotech.facade.devices.kitchen;
import org.trishinfotech.facade.devices.Light;
public class KitchenLight extends Light {
public KitchenLight() {
super("KitchenLight");
}
}
Code for Microwave class:
xxxxxxxxxx
package org.trishinfotech.facade.devices.kitchen;
import org.trishinfotech.facade.devices.Appliance;
public class Microwave extends Appliance {
protected int temp;
protected int time;
protected boolean grillOn = false;
public Microwave() {
super("Microwave");
}
public void grillOn() {
this.grillOn = true;
System.out.printf("Turning on grill of '%s'.\n", name);
}
public void grillOff() {
this.grillOn = false;
System.out.printf("Turning off grill of '%s'.\n", name);
}
public void setOnPreHeat(int temp, int time) {
this.temp = temp;
this.time = time;
System.out.printf("Setting '%s' on Pre-Heat, temprature '%d', time '%d' minutes.\n", name, temp, time);
}
public void bake(String pizzaName, int temp, int time) {
this.temp = temp;
this.time = time;
System.out.printf("Baking '%s' in '%s' for temprature '%d', time '%d' minutes.\n", pizzaName, name, temp, time);
}
public int temp() {
return temp;
}
public int time() {
return time;
}
}
Here, I have defined operations like 'Grilling On/Off', 'Pre-Heating' and 'Baking'.
Code for Refrigerator class:
xxxxxxxxxx
package org.trishinfotech.facade.devices.kitchen;
import org.trishinfotech.facade.devices.Appliance;
public class Refrigerator extends Appliance {
protected static final String PARTY = "party";
protected static final String NORMAL = "normal";
protected String mode = NORMAL;
public Refrigerator() {
super("Refrigerator");
}
public void partyMode() {
mode = PARTY;
System.out.printf("Setting '%s' Cooling to 'Party'.\n", name);
}
public void normalMode() {
mode = NORMAL;
System.out.printf("Setting '%s' Cooling to 'Normal'.\n", name);
}
}
Here I have defined 'Mode' like 'Party' for fast cooling and 'Normal' for normal cooling.
Code for LivingRoomFan class:
xxxxxxxxxx
package org.trishinfotech.facade.devices.livingroom;
import org.trishinfotech.facade.devices.Fan;
public class LivingRoomFan extends Fan {
public LivingRoomFan() {
super("LivingRoomFan");
}
}
Code for LivingRoomFireTV4KStick class:
xxxxxxxxxx
package org.trishinfotech.facade.devices.livingroom;
import org.trishinfotech.facade.devices.Appliance;
import org.trishinfotech.facade.devices.TV;
public class LivingRoomFireTV4KStick extends Appliance {
protected TV tv;
protected String appName;
protected String contentName;
public LivingRoomFireTV4KStick(TV tv) {
super("LivingRoomFireTV4KStick");
this.tv = tv;
}
// define operations for Fire TV Stick 4K
public void openApp(String appName) {
this.appName = appName;
System.out.printf("Opening '%s' on '%s'.\n", appName, name);
}
public void selectContent(String contentName) {
this.contentName = contentName;
System.out.printf("Searching '%s' on '%s'.\n", contentName, appName);
}
public void play() {
System.out.printf("Playing '%s' on '%s'.\n", contentName, appName);
}
public void closeApp() {
System.out.printf("Closing '%s' on '%s'.\n", appName, name);
}
public TV tv() {
return tv;
}
public String appName() {
return appName;
}
public String contentName() {
return contentName;
}
}
Here, I have added operations like 'Open App', 'Close App', 'Search Content' and 'Play'.
Code for LivingRoomLight class:
xxxxxxxxxx
package org.trishinfotech.facade.devices.livingroom;
import org.trishinfotech.facade.devices.Light;
public class LivingRoomLight extends Light {
protected int brightness = 50;
public LivingRoomLight() {
super("LivingRoomLight");
}
public void dim() {
brightness = 20;
System.out.printf("Dimming '%s'.\n", name);
}
public void bright() {
brightness = 100;
System.out.printf("Setting brightness of '%s' to '%d'.\n", name, brightness);
}
}
Here, I have defined operations to control light brightness by using 'dim' and 'bright'.
Code for LivingRoomSoundBar class:
xxxxxxxxxx
package org.trishinfotech.facade.devices.livingroom;
import org.trishinfotech.facade.devices.SoundBar;
import org.trishinfotech.facade.devices.TV;
public class LivingRoomSoundBar extends SoundBar {
protected TV tv;
public LivingRoomSoundBar(TV tv) {
super("LivingRoomSoundBar");
this.tv = tv;
}
public TV tv() {
return tv;
}
}
Code for LivingRoomTV class:
xxxxxxxxxx
package org.trishinfotech.facade.devices.livingroom;
import org.trishinfotech.facade.devices.TV;
public class LivingRoomTV extends TV {
protected String source;
public LivingRoomTV() {
super("LivingRoomTV");
}
public void setSource(String source) {
this.source = source;
System.out.printf("Setting Source of '%s' to '%s'.\n", name, source);
}
public String source() {
return source;
}
}
Now, when all the appliances are defined along with their operations, it's time to work on Facade Design Pattern. Suppose we like to a weekend-party at home with friends and family. Since we have various appliances at home for entertainment and food, we we write a HomeFacade to define our 'Week-End Home Party' operations.
Code for HomeFacade class:
xxxxxxxxxx
package org.trishinfotech.facade;
import java.util.List;
import org.trishinfotech.facade.devices.Fan;
import org.trishinfotech.facade.devices.Light;
import org.trishinfotech.facade.devices.SoundBar;
import org.trishinfotech.facade.devices.TV;
import org.trishinfotech.facade.devices.kitchen.CoffeeMaker;
import org.trishinfotech.facade.devices.kitchen.ElectricGrill;
import org.trishinfotech.facade.devices.kitchen.KitchenLight;
import org.trishinfotech.facade.devices.kitchen.Microwave;
import org.trishinfotech.facade.devices.kitchen.Refrigerator;
import org.trishinfotech.facade.devices.livingroom.LivingRoomFan;
import org.trishinfotech.facade.devices.livingroom.LivingRoomFireTV4KStick;
import org.trishinfotech.facade.devices.livingroom.LivingRoomLight;
import org.trishinfotech.facade.devices.livingroom.LivingRoomSoundBar;
import org.trishinfotech.facade.devices.livingroom.LivingRoomTV;
public class HomeFacade {
Fan fan;
LivingRoomFireTV4KStick stick;
Light livingRoomLight;
SoundBar soundBar;
TV tv;
CoffeeMaker maker;
ElectricGrill grill;
Light kitchenLight;
Microwave microwave;
Refrigerator refrigerator;
public HomeFacade() {
super();
fan = new LivingRoomFan();
tv = new LivingRoomTV();
stick = new LivingRoomFireTV4KStick(tv);
livingRoomLight = new LivingRoomLight();
soundBar = new LivingRoomSoundBar(tv);
maker = new CoffeeMaker();
grill = new ElectricGrill();
kitchenLight = new KitchenLight();
microwave = new Microwave();
refrigerator = new Refrigerator();
}
public void playMovieOnNetflix(String movieName) {
fan.on();
fan.increase();
livingRoomLight.on();
tv.on();
((LivingRoomTV)tv).setSource("HDMI ARC");
stick.on();
soundBar.on();
soundBar.setSoundMode("Dolby Atmos");
stick.openApp("Netflix");
stick.selectContent(movieName);
((LivingRoomLight)livingRoomLight).dim();
soundBar.volume(20);
stick.play();
}
public void prepareFood(List<String> pizzaNames) {
kitchenLight.on();
// normally refrigerator runs always. so no need to turn on.
refrigerator.partyMode(); // for fast cooling
microwave.on();
microwave.setOnPreHeat(200, 5);
microwave.grillOn();
grill.on();
maker.on();
pizzaNames.forEach(pizzaName -> microwave.bake(pizzaName, 400, 10));
}
public void stopMovie() {
stick.closeApp();
stick.off();
soundBar.off();
tv.off();
((LivingRoomLight)livingRoomLight).bright();
fan.off();
}
public void closeKitchen() {
refrigerator.normalMode();
grill.off();
maker.off();
microwave.off();
}
}
Here we have facade-methods to deal with
- Setting up Home Entertainment System to play the movie.
- Preparing Food for family and friends.
- Shutting-down Home Entertainment System when the movie completes.
- Closing the Kitchen Appliances post our Home-Party.
Now, its time to write our Main application to execute our HomeFacade and test the output:
xxxxxxxxxx
package org.trishinfotech.facade;
import java.util.Arrays;
public class Main {
public static void main(String[] args) {
HomeFacade home = new HomeFacade();
System.out.println("Weekend: Enjoying with friends and family at home...");
System.out.println("-----------------------------------------------------------------");
System.out.println("Setting up movie...");
home.playMovieOnNetflix("Spider-Man: Far From Home");
System.out.println("-----------------------------------------------------------------");
System.out.println("Preparing food...");
home.prepareFood(Arrays.asList("Napoletana Pizza", "Margherita Pizza", "Marinara Pizza",
"Chicago-Style Deep Dish Pizza"));
System.out.println("-----------------------------------------------------------------");
System.out.println("Enjoy Movie with Meal and Drink...");
System.out.println("Movie Completed.");
System.out.println("-----------------------------------------------------------------");
System.out.println("Stopping Movie...");
home.stopMovie();
System.out.println("-----------------------------------------------------------------");
System.out.println("Closing Kitchen...");
home.closeKitchen();
System.out.println("-----------------------------------------------------------------");
System.out.println("Done!");
}
}
Below is the output of the program:
Weekend: Enjoying with friends and family at home...
-----------------------------------------------------------------
Setting up movie...
Turning On 'LivingRoomFan'
Encreasing Speed of 'LivingRoomFan' to '2'.
Turning On 'LivingRoomLight'
Turning On 'LivingRoomTV'
Setting Source of 'LivingRoomTV' to 'HDMI ARC'.
Turning On 'LivingRoomFireTV4KStick'
Turning On 'LivingRoomSoundBar'
Setting Sound-Mode of 'LivingRoomSoundBar' to 'Dolby Atmos'.
Opening 'Netflix' on 'LivingRoomFireTV4KStick'.
Searching 'Spider-Man: Far From Home' on 'Netflix'.
Dimming 'LivingRoomLight'.
Setting volume of 'LivingRoomSoundBar' to '20'.
Playing 'Spider-Man: Far From Home' on 'Netflix'.
-----------------------------------------------------------------
Preparing food...
Turning On 'KitchenLight'
Setting 'Refrigerator' Cooling to 'Party'.
Turning On 'Microwave'
Setting 'Microwave' on Pre-Heat, temprature '200', time '5' minutes.
Turning On 'ElectricGrill'
Turning On 'CoffeeMaker'
Baking 'Napoletana Pizza' in 'Microwave' for temprature '400', time '10' minutes.
Baking 'Margherita Pizza' in 'Microwave' for temprature '400', time '10' minutes.
Baking 'Marinara Pizza' in 'Microwave' for temprature '400', time '10' minutes.
Baking 'Chicago-Style Deep Dish Pizza' in 'Microwave' for temprature '400', time '10' minutes.
-----------------------------------------------------------------
Enjoy Movie with Meal and Drink...
Movie Completed.
-----------------------------------------------------------------
Stopping Movie...
Closing 'Netflix' on 'LivingRoomFireTV4KStick'.
Turning Off 'LivingRoomFireTV4KStick'
Turning Off 'LivingRoomSoundBar'
Turning Off 'LivingRoomTV'
Setting brightness of 'LivingRoomLight' to '100'.
Turning Off 'LivingRoomFan'
-----------------------------------------------------------------
Closing Kitchen...
Setting 'Refrigerator' Cooling to 'Normal'.
Turning Off 'ElectricGrill'
Turning Off 'CoffeeMaker'
Turning Off 'Microwave'
-----------------------------------------------------------------
Done!
That's it!
Source Code can be found here: Facade-Design-Pattern-Sample-Code
FAQs:
- Facade Pattern Vs Adapter Pattern
- Adapter Pattern allows to make incompatible system compatible. So, we fix the compatibility issue of the system with the client application. Without Adapter, we can't use the system (incompatible). Adapter generally works with one object. Read more on Adapter Design Pattern.
- Facade Pattern simplifies the complexity of the system (compatible but complex). Without Facade, we can still use the system. But it will require knowledge of lots of minor and inner details while we do that. Facade works with entire system.
- Facade Pattern Vs Command Pattern
- Facade Pattern hides internal details and provide a simplified interface.
- Command Pattern encapsulates actions which are required perform a task (undoable set of actions). Read more on Command Design Pattern.
- Facade Pattern Vs Mediator Pattern
- Facade Pattern defines the simplifies interface to a complex system.
- Mediator Pattern provides a central communication point between components of a system.
- Facade Pattern Vs Flyweight Pattern
- Flyweight Pattern creates smaller reusable objects for the system.
- Facade Pattern creates single bigger objects to deal with the entire system.
- Facade Pattern Vs Proxy Pattern
- Proxy Pattern is similar to Facade except, it provides same interface as it's service object to make complex objects interchangeable.
- Facade Pattern Vs Abstract Factory
- Abstract Factory is like Facade except it only handles the creation part of objects of the system/subsystem.
- Facade handles system's objects operational part as well.
- Facade Pattern Vs Singleton Pattern
- Facade Object normally we create as Singleton while implement since it serves for its purpose.
I hope this tutorial demonstrates the use of facade design pattern.
Liked the article? Please don't forget to press that like button. Happy coding!
Need more articles, please visit my profile: Brijesh Saxena