carlos caballero
Angular
JavaScript
NestJS
NodeJS
TypeScript
UI-UX
ZExtra

Design Patterns: Facade

7 min read

There are 23 classic design patterns, which are described in the original book, Design Patterns: Elements of Reusable Object-Oriented Software. These patterns provide solutions to particular problems, often repeated in the software development.

In this article, I am going to describe the how the Facade Pattern; and how and when it should be applied.

Facade Pattern: Basic Idea

The facade pattern (also spelled façade) is a software-design pattern commonly used in object-oriented programming. Analogous to a facade in architecture, a facade is an object that serves as a front-facing interface masking more complex underlying or structural code. — Wikipedia

Provide a unified interface to a set of interfaces in a subsystem. Facade defines a higher-level interface that makes the subsystem easier to use.- Design Patterns: Elements of Reusable Object-Oriented Software

The main feature of this pattern is using a class which simplifies the interface of a complex system. Therefore, these are two problems that this pattern resolves:

  1. Complex subsystem are easier to use.
  2. The dependencies on a subsystem are minimized.

To sum up, the facade pattern contains several instance of different classes which must be hidden to the client. This is the way in which the interface is simplified. The UML's diagram of this pattern is the following one:

Facade Pattern

The Facade class is a middleware between modules and the external client. In the UML there is a single Facade class but the pattern can be used between different layers when the interface is very complex.

Facade Pattern: When To Use

  1. There is a complex system and you need a simple interface to communicate with it.
  2. The code is tightly coupled due to the clients need a wide knowdlege about the system. The Facade pattern allows reduce the coupled between components.
  3. The system need an entry point to each level of layered software.

Facade Pattern: Advantages

The Facade Pattern has several advantages, summarised in the following points:

  • The code is more easier to use, understand and test since the facade simplify the interface.
  • Clean code because the client/context does not use a complex interface and the system is more flexible and reusable.

Facade pattern - Example 1: A client want to use several class from different systems

I will now show you how you can implement this pattern using JavaScript/TypeScript. In our case, I have made up a problem in which there is a class named Client which defines two methods that use several classes from different packages (System1 and System2). These packages are composed by several classes which have several public methods. The following UML diagram shows the scenario that I have just described.

Facade-Problem-1-1

The client code associate is the following ones:

import { ClassA2 } from './system1/classA2';
import { ClassB } from './system1/classB';
import { ClassC } from './system2/classC';
import { ClassD } from './system2/classD';
import { ClassA } from './system1/classA';
import { ClassC3 } from './system2/classC3';

const a = new ClassA();
const a2 = new ClassA2();
const b = new ClassB();
const c = new ClassC();
const c3 = new ClassC3();
const d = new ClassD();

function methodClient1() {
  console.log('\n...methodClient1\n');
  a2.methodA2();
  b.methodB();
  c.methodC();
  d.methodD();
}

function methodClient2() {
  console.log('\n...methodClient2\n');
  a.methodA();
  c3.methodC3();
  c.methodC();
}

methodClient1();
methodClient2();

The main problem in this solution is that the code is coupled. Meaning that, the client needs to known where is and how works each class. The large list of imports is the first symptom that a facade is the solution of our problem. Another warning symptom is that client required a wide knowledge about the operation of each class.

The solution is to use an facade pattern that consists in a class (Facade) which uses System1 and System2. I.e, the new UML diagram using the adapter pattern is shown below:

Facade-Problem-1-resolved

The code associate to the client and facade are the following ones:

import { Facade } from './facade';

class Client {
  facade = new Facade();
  methodClient1() {
    this.facade.methodClient1();
  }

  methodClient2() {
    this.facade.methodClient2();
  }
}

const client = new Client();
client.methodClient1();
client.methodClient2();
import { ClassA2 } from './system1/classA2';
import { ClassB } from './system1/classB';
import { ClassC } from '../problem/system2/classC';
import { ClassD } from '../problem/system2/classD';
import { ClassA } from '../problem/system1/classA';
import { ClassC3 } from '../problem/system2/classC3';

export class Facade {
  a = new ClassA();
  a2 = new ClassA2();
  b = new ClassB();
  c = new ClassC();
  c3 = new ClassC3();
  d = new ClassD();

  methodClient1() {
    console.log('\n...methodClient1\n');
    this.a2.methodA2();
    this.b.methodB();
    this.c.methodC();
    this.d.methodD();
  }

  methodClient2() {
    console.log('\n...methodClient2\n');
    this.a.methodA();
    this.c3.methodC3();
    this.c.methodC();
  }
}

In the new code the client delegates the responsability to the facade, but the facade is doing the same functionality that client did. In fact, if the code is increasing the facade can be a antipattern called BLOB (https://sourcemaking.com/antipatterns/the-blob). So, a good idea is use a facade in each package such as you can see in the following UMLs:

Facade-Problem1-resolved-2

The code associate to the client, facade, facadeSystem1 and facadeSystem2 in this solution are the following ones:

import { Facade } from './facade';

class Client {
  facade = new Facade();
  methodClient1() {
    this.facade.methodClient1();
  }

  methodClient2() {
    this.facade.methodClient2();
  }
}

const client = new Client();
client.methodClient1();
client.methodClient2();

The client is exactly the same that in the previously version.

import { FacadeSystem1 } from './system1/facade-system1';
import { FacadeSystem2 } from './system2/facade-system2';

export class Facade {
  private facadeSystem1 = new FacadeSystem1();
  private facadeSystem2 = new FacadeSystem2();

  methodClient1() {
    console.log('\n...methodClient1\n');
    this.facadeSystem1.methodA2();
    this.facadeSystem1.methodB();
    this.facadeSystem2.methodC();
    this.facadeSystem2.methodD();
  }

  methodClient2() {
    console.log('\n...methodClient2\n');
    this.facadeSystem1.methodA();
    this.facadeSystem2.methodC3();
    this.facadeSystem2.methodC();
  }
}

The facade uses each of the facades created for each subsystem. Now the more important is that the Facade class only knows the interface that is provides by FacadeSystem1 and FacadeSystem2.

import { ClassA } from './classA';
import { ClassA2 } from './classA2';
import { ClassB } from './classB';

export class FacadeSystem1 {
  private a = new ClassA();
  private a2 = new ClassA2();
  private b = new ClassB();

  methodA2() {
    this.a2.methodA2();
  }
  methodA() {
    this.a.methodA();
  }
  methodB() {
    this.b.methodB();
  }
}
import { ClassC } from './classC';
import { ClassC3 } from './classC3';
import { ClassD } from './classD';

export class FacadeSystem2 {
  private c = new ClassC();
  private c3 = new ClassC3();

  private d = new ClassD();

  methodC() {
    this.c.methodC();
  }
  methodC3() {
    this.c3.methodC3();
  }
  methodD() {
    this.d.methodD();
  }
}

The FacadeSystem1 and FacadeSystem2 only know the classes of their package. It is very important to remind that each facade exports only the classes that are meant to be public, and these methods can be the combination of several methods between internal classes.

I have created several npm scripts that run the code's examples shown here after applying the Facade pattern.

npm run example1-problem
npm run example1-facade-solution-1
npm run example1-facade-solution-2

Facade pattern - Example 2: Pokemon and DragonBall Package together!

Another interesting problem which is resolved using Facade pattern is when there are several packages with different interfaces but they can works together. In the following UML's diagram you can see this situation:

Facade-Problem-2-resolved-2

In this case, the client uses the packages DragonballFacade and PokemonFacade. So, the client only needs to know the interface provided by theses facade. For example, DragonballFacade provides a method called genki which calculates the value of several objects working together. In other hand, PokemonFacade provides a method called calculateDamage which interacts with the rest of classes of its package.

cover-1

The code associate to the client is the following ones:

import { DragonballFacade } from './dragonball/dragonball-facade';
import { PokemonFacade } from './pokemon/pokemon-facade';

// Client/Context - System 1
console.log('DragonBall...\n');
const dragonballFacade = new DragonballFacade();
const genki = dragonballFacade.genki();
console.log(`everybody attack: ${genki}`);

// Client/Context - System 2
console.log('\nPokemon...\n');
const pokemonFacade = new PokemonFacade();
pokemonFacade.calculateDamage('passimian');
pokemonFacade.calculateDamage('poipole');
pokemonFacade.calculateDamage('mudsdale');

And the code associated to the facades are the following ones:

import { SaiyanAdapter } from './models/saiyan-adapter.model';
import { Saiyan } from './models/saiyan.model';
import { NamekianAdapter } from './models/namekian-adapter.model';
import { Namekian } from './models/namekian.model';
import { Human } from './models/human.model';
import { HumanAdapter } from './models/human-adapter.model';
import { PureRace } from './interfaces/pure-race.interface';

export class DragonballFacade {
  genki(): number {
    const gohan = new SaiyanAdapter(new Saiyan());
    const vegeta = new SaiyanAdapter(new Saiyan());
    const piccolo = new NamekianAdapter(new Namekian());
    const krilin = new HumanAdapter(new Human());

    const everybody = [gohan, vegeta, piccolo, krilin];

    return everybody.reduce(
      (power: number, pureRace: PureRace) => power + pureRace.genki(),
      0
    );
  }
}

/* ** **/

import { FightingPokemon } from './fighting-pokemon.model';
import { PoisonPokemon } from './poison-pokemon.model';
import { GroundPokemon } from './ground-pokemon.model';

export class PokemonFacade {
  pokemonFactory = {
    passimian: new FightingPokemon({
      name: 'Passimian',
      attack: 10,
      power: 10,
      defense: 10
    }),
    poipole: new PoisonPokemon({
      name: 'Poipole',
      attack: 10,
      power: 10,
      defense: 10
    }),
    mudsdale: new GroundPokemon({
      name: 'Mudsdale',
      attack: 10,
      power: 10,
      defense: 10
    })
  };

  calculateDamage(typePokemon: string): number {
    return this.pokemonFactory[typePokemon].calculateDamage();
  }
}

I have created two npm scripts that run the two examples shown here after applying the Facade pattern.

npm run example2-problem
npm run example2-facade-solution1

A great advantage in favor of the façade is developing the simplest system from one not that simple. For example, in the dragon ball package there is an adapter pattern which does not affect the correct behavior of the client. But the complexity of the Pokemon package is greater since there is a design pattern called Template-Method for the method of calculateDamage and a factory pattern for the creation of different pokemons. All this complexity is hidden by the facades and any change in these classes does not affect the client's behavior whatsoever, which has allowed us to create much more uncoupled system.

Conclusion

Facade pattern can avoid complexity in your projects, when there are several packages communicating with each other, or a client that requires the use of several classes the facade pattern is perfectly adapted.

The most important thing has not implement the pattern as I have shown you, but to be able to recognise the problem which this specific pattern can resolve, and when you may or may not implement said pattern. This is crucial, since implementation will vary depending on the programming language you use.

More more more...