Saturday, December 5, 2020

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.

Oracle Java Tutorial and Material, Oracle Java Exam Prep, Oracle Java Certification, Oracle Java Guides, Oracle Java Prep

Facade Design Pattern

◉ We may also have multiple Facade objects one dealing with few subsystems and other dealing with some other subsystems.

Oracle Java Tutorial and Material, Oracle Java Exam Prep, Oracle Java Certification, Oracle Java Guides, Oracle Java Prep

Facade Deign Pattern with multiple sub-systems

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:

package org.trishinfotech.facade.devices;
 
public abstract class Appliance implements Comparable {
 
    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.
    @Override
    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:

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:

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:

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:

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:

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:

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:

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:

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:

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:

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:

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:

package org.trishinfotech.facade.devices.livingroom;
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: 

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:

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:

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 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:

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 grill of 'Microwave'.
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!

Related Posts

0 comments:

Post a Comment