What are Object Creational Patterns?

0
767
Object Creational Patterns

The first part of this 12-part series of articles on software design focuses on ‘creational patterns’. The simple words and examples used here will help newbies understand these patterns better.

The wheel is said to be one of man’s greatest inventions. However, reinventing the same wheel again doesn’t make any sense. A better approach is to come up with a new idea using the wheel. The same logic holds good for software design as well.

Most of the design problems that we attempt to solve might have been solved already by someone else. It makes more sense to use those known design ideas to solve a bigger and a newer problem than breaking heads in reinventing the same idea again.

The famous Gang of Four (Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides) published a good number of reusable design ideas way back in the mid-90s, in a book titled ‘Design Patterns: Elements of Reusable Object-Oriented Software’. As the title suggests, they called the design ideas ‘design patterns’ and the book focused on the area of object-oriented design.

Since then, several experts have published hundreds of design patterns applicable to various other design areas like domain driven design, enterprise integration, distributed computing, microservices, system architecture, transaction management, data management, etc.

Though there is a lot of literature available on these design patterns, it is full of jargon. Because of this, many greenhorns find it really difficult to understand and apply them. This 12-part series attempts to present a few of the patterns in simple words with the help of illustrations and case studies.

Providers and consumers
Let us build a user management system (UMS) that maintains a list of users by their phone numbers. Does it look trivial? Of course, yes! However, this system is good enough for us to understand a couple of design patterns. Figure 1 presents a basic design for the UMS.

Figure 1 Basic design for a user management system
Figure 1: Basic design for a user management system

The package com.glarimy.ums has a class InMemoryUserRepository that holds the user entries in a map. The phone number of the user acts as the key while the name of the user becomes the value. The class also offers operations to add and find entries. The code in Java looks like this:

package com.glarimy.ums;
public class InMemoryUserRepository {
    private Map<Long, String> entries;
    
    public InMemoryUserRepository() {
        entries = new HashMap<Long, String>();
    }

    public void add(Long phone, String name) {
        entries.put(phone, name);
    }

    public String find(Long phone) {
        return entries.get(phone);
    }
}

Notice that the class does not have the main() method. It is just a reusable class that can be used by any other class. The Application is one such class that uses InMemroryUserRepository.

package com.client;

import com.glarimy.ums.InMemoryUserRepository;

public class Application {
public static void main(String[] args) {
InMemoryUserRepository repo = new InMemoryUserRepository();
repo.add(9731423166L, “Krishna”);
String name = repo.find(9731423166L);
System.out.println(“Found “ + name);
}
}

Note that the Application is in the package com.glarimy.client and is using an instance of InMemroryUserRepository. In other words, the Application is consuming the services provided by InMemroryUserRepository. Every system is full of such providers and consumers of services.

Service contract
The current implementation of InMemroryUserRepository is not persisting the user entries in a database. Instead, it is merely storing them in memory, which makes it a simple cache.

Assume that we are going to sell the UMS to multiple customers. What if some of the customers want database support? There are two options.

The first option is to modify the code of InMemroryUserRepository to add database support. However, this won’t help us because the other customers do not want this support.

The second option is to develop another class (say PersistentUserRepository) with database support. This way, we can build two different jars (one with InMemroryUserRepository and the other one with PersistentUserRepository) and distribute them as per the customer preference.

In other words, we are planning to have two providers for the same contract — to manage the user data. One provider is with database support and the other is without it.

With two providers at its disposal, the Application can choose which one to consume. Conceptually, this looks good! However, the current implementation of the Application clearly refers InMemroryUserRepository only. Any change of the provider demands a change in the code of the consumer. This is called ‘tight coupling’. Does it make sense to couple the providers and consumers tightly? Would you like to make changes to your laptop every time you want to use a different USB device? No, right? Tight coupling is bad!

What makes the laptop and the USB devices couple loosely? The answer lies in the USB interface, which acts as the contract between laptops and various devices.

Let us build such a contract for UMS.

package com.glarimy.ums;

public interface UserRepository {
public void add(Long phone, String name);
public String find(Long phone);
}

The interface UserRepository offers a contract with the specifications for the add() and find() services. It does not offer any implementation. That job is left for the providers of the contract.

The InMemoryUserRepository can be made as a provider of this contract by refactoring it as an implementation of the interface.

public class InMemoryUserRepository implements UserRepository {
	...
}

As a consumer, the Application can now refer to the interface as follows:

UserRepository repo = new InMemoryUserRepository();
repo.add(9731423166L, “Krishna”);
String name = repo.find(9731423166L);
System.out.println(“Found “ + name);

It can be observed that the code is written based on the contract of the interface. It is always a good idea for consumers to refer only to the interfaces, not the providers. This practice is popular as ‘coding against the interfaces’.

Does this code solve the problem of tight coupling? Not completely. The first line still has an explicit reference to the provider. That must go! However, before addressing it, let’s take a close look at InMemoryUserRepository.

Singleton
The current implementation of InMemoryUserRepository offers a constructor that is accessible publicly. It implies that consumers can use the new operator to create any number of instances of it. That can create problems!

For example, the Application creates an instance of InMemoryUserRepository to add user entries. Unknowingly, it can create one more instance of it in another part of the code, to find the user entries. However, the map of the second instance has nothing to do with the map of the first instance. Hence, the Application fails to find any user entries in the second instance. The point is that classes like InMemoryUserRepository must be created only once across the application. In other words, the InMemoryUserRepository must be a ‘singleton’.

How do you convert the InMemoryUserRepository as a singleton so that it is possible to instantiate it only once in a JVM?

The first step is to make the constructor private, to prevent the consumers from instantiating the objects using the new operator.

private InMemoryUserRepository() { ... }

The second step is to introduce a new method, say getInstance(), for creating and returning an instance of InMemoryUserRepository in a controlled way. The getInstance() method must be available at the class level so that the consumers can directly invoke it. This means, getInstance() must be static.

public static InMemoryUserRepository getInstance() {...}

With the presence of the above method, the Application can get an instance as follows:

UserRepository repo = InMemoryUserRepository.getInstance();

But how can the getInstance() method ensure that it creates and returns only one instance of InMemoryUserRepository? Simple! It must create an instance in the beginning and keep returning the same instance every time. The following code does the job!

private static InMemoryUserRepository INSTANCE = null;

public static InMemoryUserRepository getInstance() {
if (INSTANCE == null)
INSTANCE = new InMemoryUserRepository();
return INSTANCE;
}

By designing the InMemoryUserRepository as a singleton, we ensure that the consumers share the one and only instance of the InMemoryUserRepository, no matter how many times the method getInstance() is called. Take a look at this code:

UserRepository first = InMemoryUserRepository.getInstance();
UserRepository second = InMemoryUserRepository.getInstance();
first.add(9731423166L, “Krishna”);
String name = second.find(9731423166L);

The first and second are exactly the same objects. This works perfectly. We can follow the same pattern to convert any class as a singleton.

Factory
We thought that tight coupling is a problem when the Application wants to choose between InMemoryUserRepository and PersistentUserRepository. But the tight coupling can be a problem with just InMemoryUserRepository also. Let’s see how!

Earlier, the Application gets the object of InMemoryUserRepository as follows:

UserRepository repo = new InMemoryUserRepository();

But once InMemoryUserRepository is refactored as a singleton, the Application changes like this:

UserRepository repo = InMemoryUserRepository.getInstance();

Just because of an internal change in the InMemoryUserRepository, the code of the Application is also forced to change. This proves again that tight coupling is bad.

How else can the consumer and provider be connected? Like the way it happens in the real world, instead of the consumers creating the providers, they must go to a factory and ask for a provider of a contract. After all, the object-oriented design mimics the real world! The factory must take the responsibility of creating and supplying the provider to the consumers. The UserRepositoryFactory does the job nicely.

package com.glarimy.ums;
public class UserRepositoryFactory {
	public static UserRepository get() {
		return InMemoryUserRepository.getInstance();
	}
}

This offers a static method get(), which the consumers can invoke to get hold of an instance of a provider that implements the UserRepository interface. What else does the Application want?

UserRepository repo = UserRepositoryFactory.get();

Now the Application has ZERO references to the InMemoryUserRepository. In other words, the consumer and the provider are no longer tightly coupled. A factory always takes the responsibility of supplying providers to the consumers.

However, a system may have several contracts with several providers. Should we have different factories for different contracts, since the return type of the get() method is specific to a given interface?

public static Object get(String key) throws Exception {
if (key == “user.repository”)
return InMemoryUserRepository.getInstance();
throw new Exception();
}

In the above implementation, the get() method is declared to return an Object, instead of just a UserRepository. You know that java.lang.Object is a superclass of everything in Java. In other words, the get() method is now equipped to return any object of any class. But how does it choose the object to be returned? This depends on the input from the consumer.

The onus is now on the consumer to specify its preference and also to cast the returned object to the interface it is referring to.

UserRepository repo = (UserRepository) ObjectFactory.get(“user.repository”);

The design looks much better now with interfaces, implementations, singletons and factories.

Framework
There is always scope for improvement, provided that time and other resources are available. Let’s look at one such avenue for improvement.

Though we made the Application and InMemoryUserRepository loosely coupled, the ObjectFactory is itself tightly coupled with the providers. Not a good sign, isn’t it?

At least in Java, we can overcome that problem by using reflection. Look at the following snippet of get() method of ObjectFactory that uses reflection.

public static Object get(String key) throws Exception {
Properties props = new Properties();
props.load(new FileReader(“config.properties”));
String className = props.getProperty(key);
Class<?> claz = Class.forName(className);

try {
return claz.newInstance();
} catch (IllegalAccessException | InstantiationException e) {
return claz.getMethod(“getInstance”).invoke(claz);
}
}

The implementation makes a few assumptions:
1. There will be a configuration file named ‘config.properties’ which supplies the class names against keys.
2. The classes that are referred to in the configuration file are available on the classpath for loading.
3. The classes either have a publicly accessible default constructor or a statically available getInstance() method to get an instance of the class.
The content of the config.properties looks like the following:

user.repository=com.glarimy.ums.InMemoryUserRepository

As long as these expectations are met, the ObjectFactory nicely supplies the objects of any class. Suddenly, the ObjectFactory looks like a class that is useful in every application, not just for UMS, as it does not have any reference to anything in the UMS. If so, why don’t we move it out of the com.glarimy.ums package? What is the apt name for a unit that contains such classes with universal appeal? Framework! We better move the ObjectFactory to a new package named com.glarimy.framework.

Readability and annotations
Frameworks usually make several assumptions and expect developers to build the rest of the system while satisfying those assumptions. Having a static getInstance() method on a singleton is such an assumption. But, frankly speaking, the method name can be anything else. Insisting on a certain name to a method is a bit crude. To give freedom to the developers in naming the methods as per their preference, we can improve the framework a bit more.

How about the following code, with @Singleton annotation that clearly marks the InMemoryUserRepository as a singleton, along with the name of the static method for creating the objects?

@Singleton(getter = “getInstance”)
public class InMemoryUserRepository implements UserRepository {
...
}

It both improves the code readability and gives the flexibility to supply the name of the static method. The annotation is defined as follows:

package com.glarimy.framework;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Singleton {
public String getter();
}

Notice that this annotation also has nothing to do with the UMS. So, naturally, it fits into the framework.

With the introduction of the annotation, the ObjectFactory needs an improvement to check for the presence of the annotation before falling back on the presence of the getInstance() method.

Singleton singleton = claz.getAnnotation(Singleton.class);
if (singleton != null) {
String getter = singleton.getter();
return claz.getMethod(getter).invoke(claz);
}
return claz.getMethod(“getInstance”).invoke(claz);

Factory method
Though the way we implemented the factory pattern is good, it makes even more sense to make it open for extension. In other words, design a contract for the factory and leave it open for several possible implementations. Here is the contract (i.e., interface):

package com.glarimy.framework;

public interface Factory {
public Object get(String key) throws Exception;
}


Here is an implementation for the Factory interface. We replaced the static method with an instance method. Also, the constructor accepts the name of the configuration file, instead of hard-coding it.

public class ObjectFactory implements Factory {
private Properties props;
public ObjectFactory(String source) throws Exception {
props = new Properties();
props.load(new FileReader(source));
}

@Override
public Object get(String key) throws Exception {
...
}
}

With these changes in the framework, the Application looks like this:

Factory factory = new ObjectFactory(“config.properties”);
UserRepository repo = (UserRepository) factory.get(“user.repository”);

Figure 2 depicts the UMS that we have designed so far.

Figure 2: Refactored design for user management system
Figure 2: Refactored design for user management system

In this first part of the 12-part series, we covered best practices like having contracts, coding against interfaces, and coupling the consumers and providers loosely. We also understood the intentions behind singleton, factory, and factory method patterns, and found that there is more than one way of implementing them. Since these patterns are related to creating objects, they are categorised as ‘creational patterns’ by the Gang of Four. In the next part, we will be looking at a couple of ‘structural patterns’.

LEAVE A REPLY

Please enter your comment!
Please enter your name here