Design Patterns in Java

One of the unchanging facts of life is that change is the undying constant in every software lifecycle - one that you cannot run away from. The challenge is to adapt to this change with minimum latency and maximum flexibility.

The good news is that someone has probably already solved your design problems and their solutions have evolved into best practices; these agreed-upon best practices are referred to as "Design Patterns". Today, we're going to explore two popular design patterns, and learn how good design can help you write code that is squeaky clean and extensible.


The Adapter Design Pattern

Let's assume that you have an existing legacy system. You're now tasked with making it work with a new third-party library, but this library has a different API compared to the last one you were using. The legacy system now expects a different interface than what the new library provides. Of course, you could be brave (read, foolish) enough to think about changing your legacy code to adapt to new interface but as with every legacy system -- never, ever.

Incompatible Interfaces

Adapters to the rescue! Simply write an adapter(a new wrapping class) in between the systems, which listens for client requests to the older interface, and redirects or translates them into calls to the new interface. This conversion can either be implemented with inheritance or composition.

Great design is not just about re-usability, but about extensibility.

Adapters help incompatible classes work together by taking an interface and adapting it to one that the client can parse.

Adapters to the Rescue !!

Adapters In Action

Enough chit-chat; let's get down to business, shall we? Our legacy software system uses the following LegacyVideoController interface to control the video system.

The client code which uses this controller looks like:

User Requirements Change!

There's nothing new here, really - it happens quite frequently. User requirements are prone to change all the time and our legacy system now needs to work with a new video controller, having the following interface:

As a result, the client code breaks, as this new interface is not compatible.

Adapter Saves the Day

So, how do we handle this modified interface without changing our legacy code? You know the answer now, don't you? We write a simple adapter to modify its interface to adapt to the existing one as below:

This adapter implements the target interface, which the client expects so there is no need to change the client code. We compose the adapter with an instance of the adaptee interface.

This "has-a" relationship allows the adapter to delegate the client request to the actual instance.

Adapters also help in decoupling the client's code and the implementation.

We can now simply wrap the new object in this adapter and be done with it, without making any changes to the client code as the new object is now converted/adapted to the same interface.

An adapter can be a simple pass through or can be intelligent enough to provide some add-ons depending on the complexity of the interface to support. Similarly, one adapter can be used to wrap more than one object if the target interface is complex and the new functionality has been split across two or more classes.

Comparison With Other Patterns

  • Decorator : Decorator changes the interface and wraps an object around by adding new responsibility. On the other hand, adapter is used to convert the adaptee interface to target interface which is understood by the client.
  • Facade : Facade works by defining a totally new interface abstracting the complexity of earlier interfaces while adapter is used to enable communication between incompatible interfaces by converting one into another.
  • Proxy : Proxy provides the same interface. Whereas adapter provides a different interface to its subject.
  • Bridge : Bridge is designed upfront to let the abstraction and the implementation vary independently but adapter is used to adapt to an existing interface by delegating the request to adaptee.

The Singleton Design Pattern

Though there are many patterns which deal with creation of objects, one specific pattern stands out. Today we're going to inspect one of the most simple, yet misunderstood, ones: the Singleton pattern.

As the name suggests, the purpose of the singleton is to create a single instance of the class and provide global access to it. Examples can be an application level Cache, an object pool of threads, connections etc. For such entities, one and only instance must suffice else they threaten the stability and defeat the purpose of the application.

Implementing the Singleton Pattern

A bare-bones implementation in Java would look like:

In our example, the class holds a static member of the same type as that of the class, which is accessed via a static method. We make use of Lazy Initialization here, delaying the initialization of the cache, until it is actually needed at runtime. The constructor is also made private so that a new instance of this class cannot be created using the new operator. To fetch the cache, we invoke:

It works perfectly fine as long as we are dealing with a single-threaded model. But life, as we know it, is not that simple. In a multi-threaded environment, you need to synchronize the lazy initialization or just do away with it, by creating the cache as soon as the class is loaded, either by using static blocks or initializing while declaring the cache.

Double Checked Locking

We synchronize the lazy initialization to make sure that the initialization code is run only once. This code works with Java version 5.0 and above due to idiosyncrasies associated with the implementation of synchronized and volatile in Java.

We make the instance variable volatile so that the JVM prevents out-of-order writes for it. We also do a double null check (hence the name) for instance while synchronizing the initialization so that any sequence of 2 or more threads does not corrupt the state or result in creation of more than one instance of the cache. We could have synchronized the whole static accessor method instead, but that would have been an overkill as synchronization is only needed until the object is fully initialized; never again while accessing it.

No Lazy Initialization

An easier way would be to do away with the benefits of lazy initialization, also resulting in cleaner code:

As soon as the class loads and the variables are initialized, we invoke the private constructor to create the one and only instance of the cache. We lose the benefits of lazily initializing the instance but the code is much cleaner. Both methods are thread-safe and you can choose the one suitable for your project environment.

Safeguard Against Reflection and Serialization

Depending on your requirement, you might also want to protect against:

  • Code using Reflection API to invoke the private constructor, which can be dealt with by throwing an exception from the constructor, in case it is called more than one time.
  • Similarly serializing and de-serializing the instance might also result in two different instances of our cache, which can be handled by overriding the readResolve() method from the Serialization API

Design Patterns are Language Agnostic

The title of our tutorial is a bit misleading, I admit, as design patterns are really language-agnostic. They are simply a collection of the best design strategies developed to counter recurring problems faced in software design. Nothing more, nothing less.

For example, below is a quick look at how we could implement a singleton in Javascript. The intent remains the same: controlling the creation of the object and maintaining a global point of access, but the implementation differs with the constructs and semantics of each language.

To quote another example, jQuery also makes heavy use of the Facade design pattern, abstracting away the complexity of a subsystem and exposing a simpler interface to the user.


Closing Remarks

Not every problem requires the use of a specific design pattern

A word of caution is necessary: do not overuse! Not every problem requires the use of a specific design pattern. You need to carefully analyze the situation before settling on a pattern. Learning design patterns also helps in understanding other libraries like jQuery, Spring etc which make heavy use of many such patterns.

I hope that, after reading this article, you're able to get a step closer in your understanding of design patterns. If you have any questions or want to learn additional design pattern, please let me know within the comments below, and I will do my best to answer your questions!

Tags:

Comments

Related Articles