6 Design Patterns in Java that Solve Major Problems!

 PATTERN PROBLEM


In a previous post, I showed you how the OOP world looks like. OOP is a pretty convenient paradigm for identifying requirements while also making them easier to understand.

Different OOP concepts like inheritance and polymorphism have a number of use cases in popular applications.

However, in some situations, OOP on its own is not enough. To implement a particular feature, you may need to use OOP concepts in a way that makes your code better and more understandable.

In this post, you’ll learn 5 design patterns that can make your life better as a Java programmer. Before that, let’s get two things out of the way.

Why Design Patterns?


Let’s take a class Boat that has different sub classes representing the types of boats. A Boat object performs two actions, sway and roll.

abstract class Boat {
void sway() { ... }
void roll() { ... }
abstract void present();
}

The sway() and roll() methods are inherited by each sub class as they are the same for each type of boat. The present() method is abstract as each boat has a specific way of presenting itself.

class FishBoat extends Boat {
void present() { ... }
}
void DinghyBoat extends Boat {
void present() { ... }
}

The two boats, FishBoat and DinghyBoat extend the Boat class. So far, everything is working fine. You can also add new boats as you want.

Now, there’s a new feature request where you need to add a dive functionality to a new boat, SubBoat. With this feature, the boat turns into a submarine and dives underwater. The question is, where do you define the dive() method?

There are two possible solutions.

Inheritance

This is a pretty obvious one. You can define the dive() method in the Boat class which will then be inherited by SubBoat. This ensures code reusability and code maintainability.

What’s the problem with this solution? Well, you have added the dive() method to the superclass which means all the subclasses inherit this feature.

But, we don’t want to see the fish boat turn into a submarine and dive. The point is, not all classes need to inherit your new feature and they should not.

Interfaces

The inheritance solution did not serve our purpose, so you could try creating a Diveable interface that defines the dive() method. Only those boats that are supposed to have the dive feature will implement the interface and override the dive() method.

This solves the problem of the previous solution. Only the necessary classes will inherit the dive() method. But this presents a whole new set of problems.

Since you are using an interface, you don’t have an actual implementation of the method. So, you’ll need to implement the method inside the sub-class.

This doesn’t seem bad at first, but don’t be fooled. What if you have 50 more classes that want the new feature? You’ll have to implement the same method 50 times.

This is a nightmare as it’s too much work and completely ruins code maintainability.

From the above example, you have understood using OOP principles in a conventional way does not help solve the problem effectively. We need a new way of solving this and many such problems. This is where Design Patterns come into play.

What are Design Patterns?


When the conventional approaches don’t work, you know it’s time to craft new solutions. This is what software development is all about, changing your current, ineffective ways to come up with more effective approaches.

Let’s look at the problems we encountered. In inheritance, you added a new functionality, but in the process, you also changed the existing ones. The boats that weren’t supposed to dive suddenly find themselves inheriting the dive() method.

Using an interface did seem to solve the problem but it introduced new issues. For every class needing the new feature, you had to implement the dive() method. If there’s even a small change in the feature, you’ll need to make the change across all the classes implementing the method.

According to design principles, when you have got some code that is a new requirement, the behaviour needs to be encapsulated and kept separate from the existing code so that it doesn’t lead to unexpected consequences.

This principle serves as a foundation for many design patterns, some of which, you will see in this post. You will also look at the design pattern used to solve the above problem.

1. Singleton Pattern

In this pattern, you can create only one instance of a class. Even if you create multiple reference variables, all of them will point to the same object.

Pretty straightforward, right? But how can you make this possible? How do you ensure that only one object gets created? You’ll understand with an example.

Let’s have a class Probe, with its instance variables and methods.

class Probe {
// Instance variables

// Important methods
}

Our class Probe does not like to have multiple objects. So, we make its constructor private.

private Probe() { 
// Initialize variables here
}

Now, this constructor can only be called from inside the class. Create a static method getInstance().

This method takes the reference variable of the Probe class and checks whether the object was already instantiated. If not, then a new one is created and returned to the client.

private static Probe getInstance(Probe probe) {
if(probe == null)
probe = new Probe();

return probe;
}

Since getInstance() is a static method, it can only be called at the class level and not at the object level. Every time this method is called, it returns the same object. Thus, the pattern is able to block you from creating multiple objects.

The singleton pattern is used for objects where only one instance is needed. For example, objects for registry settings and logging only need one instance or else, they might cause unintended side effects.

2. Observer Pattern

Let’s say you follow a social media page. Whenever this page adds a new post, you would like to be notified of the same.

So, in the event that one object (page) does an action (adding a post), another object (follower) is notified. This scenario can be implemented through Observer Pattern.

Let’s have a class Page and an interface Follower. The page can have different types of followers, a normal user, a recruiter and an official. We’ll have a class for each follower type and all the classes will implement the Follower interface.

Here, Page class is the Subject and the followers are the Observer classes. The observer pattern defines a one to many relationship with the Subject and the Observers. If the subject changes its state (page adds a new post), all the observers i.e followers are notified.

The class Page will have the following methods:

  • registerFollower() : This method registers new followers.
  • notifyFollowers() : This method notifies all the followers that the page has a new post.
  • getLatestPost() and addNewPost(): Getter and setters for the latest post on the page.

On the other hand, the Follower interface only has one method update() that will be overriden by the follower types that implement this interface, also called the Concrete Observers.

interface Follower {
public void update();
}

The update() method is called when the subject needs to notify the observer about the state change i.e. a new post.

Let’s implement the Page class i.e. the subject.

class Page {
private ArrayList<Follower> followers;
String latestPost;

public Page() {
followers = new ArrayList();
}

public void registerFollower(Follower f) {
followers.add(f);
}

public void notifyFollowers() {
for(int i = 0; i < followers.size(); i++) {
Follower follower = followers.get(i);
follower.update();
}
}

public String getLatestPost() {
return latestPost;
}

public void addNewPost(String post) {
this.latestPost = post;
notifyFollowers();
}

}

In this class, we have a list of all followers. When a new follower wants to follow the page, it calls the registerFollower() method. latestPost holds the new post added by the page.

When a new post is added, the notifyFollowers() method is called where it iterates over each follower and notifies them by calling the update() method.

Now, let’s implement our first kind of follower, User.

class User implements Follower {
Page page;
public User(Page page) {
this.page = page;
page.registerFollower(this);
}
public void update() {
System.out.println("Latest post seen by a normal user: " + page.getLatestPost());
}
}

When a new User object is created, it takes the page that it wants to follow and registers for the same. When the page adds a new post, User is notified through the update() method that the User can implement in its own way.

Let’s have two more classes that want to follow the page.

class Recruiter implements Follower {
String company;
// Rest is the same as User class
}
class Official implements Follower {
String designation;
// Rest is the same as User class
}

Let’s test the pattern in the main class. First, create a page and add a new post.

Page page = new Page();
page.addNewPost("I am feeling lucky!");

No one has followed the page yet, so this won’t notify anyone. Now, a normal user follows the page which adds another post.

User user = new User(page);
page.addNewPost("It's a beautiful day!");

Now, the user will be notified and give the following output.

Latest post seen by a normal user: It's a beautiful day!

Next, the recruiter and the official also follow the post.

Recruiter recruiter = new Recruiter(page);
Official official = new Official(page);
page.addNewPost("Ready to go for a run!!");

All three of them will be notified of this acitivity.

Latest post seen by a normal user: Ready to go for a run!!
Latest post seen by a recruiter: Ready to go for a run!!
Latest post seen by an official: Ready to go for a run!!

3. Strategy Pattern

Now, let’s come back to the boat problem. We wanted to add a dive functionality to some boats. The two methods, inheritance and method overriding did not serve our purpose.

If you recall the design principle, we need to separate the changing code from what already exists. The only part changing is the dive() behaviour, so we create an interface Diveable and create two more classes that implement it.

interface Diveable {
public void dive();
}
class DiveBehaviour implements Diveable {
public void dive() {
// Implementation here
}
}
class NoDiveBehaviour implements Diveable {
public void dive() {
// Implementation here
}
}

Now, in your Boat class, create a reference variable for the interface and have a method performDive() that calls this dive() method.

abstract class Boat {
Diveable diveable;
void sway() { ... }
void roll() { ... }

abstract void present();

public void performDive() {
diveable.dive();
}
}

The FishBoat and DinghyBoat classes aren’t supposed to have the dive behaviour, so they’ll inherit the NoDiveBehaviour class. Let’s see how.

class FishBoat extends Boat {
...

public FishBoat() {
diveable = new NoDiveBehaviour();
}
...
}

When the reference variable diveable is instantiated to the NoDiveBehaviour object, the FishBoat class inherits the dive() method from this class.

For the new class SubBoat, a new behaviour can be inherited.

class SubBoat extends Boat {
...

public FishBoat() {
diveable = new DiveBehaviour();
}
...
}

Now, let’s test the functionality.

Boat fishBoat = new FishBoat();
fishBoat.performDive();

When performDive() is called, it calls the dive method of the NoDiveBehaviour class.

Boat subBoat = new SubBoat();
subBoat.performDive();

This performs a completely different action as it calls the actual dive behavior. Hence, our new boat turns into a submarine and takes a dive.

4. Decorator Pattern

Sometimes, you want to make a few modifications to your functionality. While doing that, you need to make sure not to change the existing one.

We’ll take the example of a class Car that is extended by two classes, Ford and Audi. It has a build() method along with other important methods. This method is abstract as each car has its own implementation.

abstract class Car {
abstract void build();
}
class Ford extends Car {
public void build() {
System.out.println("Ford built");
}
}

class Audi extends Car {
public void build() {
System.out.println("Audi built");
}
}

Everything is working fine. However, the clients want to make a few modifications, like adding colorful headlights, adding a spoiler or adding nitrous. How will you make these additions?

One way is to create different subclasses for cars with these modifications,  and so on. But you can see how quickly this becomes a big problem. There is no limit on the number of possible combinations.

Another way is to have the each mod as an instance variable and call build() on the required mods. However, with each new mod, you’ll need to keep modifying the existing code, thus increasing the chances of introducing bugs.

There is a better and a more flexible way of doing this. You can define separate classses for each mod and wrap your car around that object. What does this mean? You’ll understand it shortly.

Create a class CarModifications that extends Car. I’ll refer to this class as mod class.

abstract class CarModifications extends Car {
Car car;

public CarModifications(Car car) {
this.car = car;
}
}

By creating a Car object inside CarModifications, you are wrapping Car inside the mod class. The mod class is an abstract class and is extended by three more classes: ColorLightSpoiler and Nitrous.

class Spoiler extends CarModifications {
public Spoiler(Car car) {
super(car);
}

public void build() {
car.build();
addSpoiler();
}

void addSpoiler() {
System.out.println("Spoiler built");
}
}

This is a specific mod class wrapping Car. It implements the build() method by first building the car and then adding a spoiler to it. The other two mod classes have a similar implementation.

Now, let’s test this pattern. We’ll create an Audi and add a spoiler to it.

Car audi = new Audi();
Car audiWithSpoiler = new Spoiler(audi);
audiWithSpoiler.build();

After you create a Car object, you use the same instance to create a new Car object with a spoiler added to it. Call the build() method to execute the pattern giving the following output.

Audi built
Spoiler built

If you want a car with nitrous too, create another Car object.

Car audiWithMods = new Nitrous(audiWithSpoiler);
audiWithMods.build();

Output:

Audi built
Spoiler built
Nitrous built

5. Factory Pattern

This pattern suggests a different way of instantiating objects that provide more flexibility to changing requirements. When you extend an abstract class and implement its abstract methods, you create a concrete class.

With new requirements, the number of concrete classes in your code may increase. This makes it difficult for you to modify your code. So, following the same principle, changing behavior should be kept separate from the existing one, the factory pattern is implemented.

This has been a really long post, so I’ll discuss this pattern in a future post.

Conclusion

Coding some scenarios may become problematic with conventional methods. It’s important to keep trying different methods and being open to change the way you approach a problem.

In this post, I started with the reason we need design patterns and the principles they follow. Then, I explained four design patterns with examples and code blocks. Hopefully, this made you understand them better.

. Till then, Goodbye!!

References

This article has been inspired by the book,  (not an affiliate link). I suggest you go through this book as they have explained each and every concept in a fun and light-hearted way.

Level Up Coding

Thanks for being a part of our community! Before you go:

  • 👏 Clap for the story and follow the author 👉

Comments

Popular posts from this blog

Top 3 Reasons to Still Learning Java in Now a Days

Mathematics and Coding: Understanding the Connection

3 Must-Know Programming Concepts