The facade design pattern

Whenever we are building libraries or big systems, we quite often depend on other libraries and functionality. Implementing methods sometimes requires the use of multiple classes at the same time. This requires knowledge. Whenever we build a library for someone, we usually try and make it simpler for the users by assuming they do not have (and do not need) as extensive knowledge as we do. Additionally, developers make sure that components are easy to use throughout their application. This is where the facade design pattern can become useful.

Note

Its purpose is to wrap a complex system with a simpler interface in order to hide the usage complexities and ease the client interaction.

We already looked at other design patterns based on wrapping. While the adapter design pattern transforms one interface to another and the decorator adds extra functionality, the facade makes things simpler.

Class diagram

For the class diagram, let's imagine the following setting: we want our users to be able to download some data from a server and get it de-serialized in the form of objects. The server returns our data in encoded form, so we should decode it first, then parse it, and finally return the right objects. This involves many operations and makes things complicated. That's why we use a facade design pattern.

Class diagram

When clients use the preceding application, they will just have to interact with the DataReader. Internally, it will take care of downloading, decoding, and de-serializing the data.

Code example

The preceding diagram shows the DataDownloader, DataDecoder, and DataDeserializer as composed objects inside DataReader. This is straightforward and clear to achieve—they can be either created with their default constructors, or they could be passed as parameters. For the code representation of our example, however, we have chosen to use traits instead of classes and mix them in with the DataReader class.

Let's first take a look at the DataDownloader, DataDecoder, and DataDeserializer traits:

trait DataDownloader extends LazyLogging {
  
  def download(url: String): Array[Byte] = {
    logger.info("Downloading from: {}", url)
    Thread.sleep(5000)
//    {
//      "name": "Ivan",
//      "age": 26
//    }
//    the string below is the Base64 encoded Json above.
    "ew0KICAgICJuYW1lIjogIkl2YW4iLA0KICAgICJhZ2UiOiAyNg0KfQ==".getBytes
  }
}

The DataDecoder trait is as follows:

trait DataDecoder {
  
  def decode(data: Array[Byte]): String = new String(Base64.getDecoder.decode(data), "UTF-8")
}

The following code snippet is of the DataDeserializer trait:

trait DataDeserializer {
  implicit val formats = DefaultFormats
  
  def parse[T](data: String)(implicit m: Manifest[T]): T = JsonMethods.parse(StringInput(data)).extract[T]
}

The preceding implementations are pretty straightforward and they are separated from each other, as they deal with different tasks. Anyone can use them; however, it requires some knowledge and makes things more complicated. That's why we have a facade class called DataReader:

class DataReader extends DataDownloader with DataDecoder with DataDeserializer {

  def readPerson(url: String): Person = {
    val data = download(url)
    val json = decode(data)
    parse[Person](json)
  }
}

This example clearly shows that instead of using three different interfaces, we have now a simple method to call. All complexity is hidden inside this method. The next listing shows a sample usage of our class:

object FacadeExample {
  def main(args: Array[String]): Unit = {
    val reader = new DataReader
    System.out.println(s"We just read the following person: ${reader.readPerson("http://www.ivan-nikolov.com/")}")
  }
}

The preceding code makes the use of our libraries, which are hidden from the client, really easy. Here is a sample output:

Code example

Of course, in the preceding example, we could have used classes inside DataReader instead of mixing traits in. This really depends on the requirements and should yield the same results anyway.

What is it good for?

The facade design pattern is useful when we want to hide the implementation details of many libraries, make an interface much easier to use, and interact with complex systems.

What is it not so good for?

A common mistake some people could make is try and put everything into a facade. This is something that usually doesn't help and the developers remain with as complex a system, if not more, as before. Moreover, facade could prove to be restrictive for those users who have enough domain knowledge to use the original functionality. This is especially true if facade is the only way to interact with the underlying system.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.21.106.7