Understanding Java Design Patterns
Design patterns are common solutions to recurring software design problems. They are proven, reusable templates that help developers avoid reinventing the wheel and solve common problems in a standardized way. In this blog post, we’ll explore three well-known design patterns in Java: Singleton, Factory, and Facade. We will explore the structure, use cases, and practical examples of these patterns, helping you understand when and how to use them effectively in your Java applications. Learn Understanding Java Design Patterns with real-world examples to improve code reusability, scalability, and maintainability in Java software development.
1. Singleton Design Pattern
The Singleton design pattern is a creational design pattern that ensures a class has only one instance throughout the lifetime of an application and provides a global point of access to that instance. Imagine you have a class that should only be instantiated once during the entire lifecycle of an application. You may want to avoid creating multiple instances because they would lead to inconsistent results or unnecessary resource consumption. The Singleton pattern ensures that only a single instance of the class exists.
Use case:
This pattern is particularly useful when we want to limit the instantiation of a class to a single object, such as when managing a shared resource e.g., database connection, logger, Caching mechanisms, or Configuration management.
Program:
public class Singleton {
// Step 1: Private static variable to hold the single instance.
private static Singleton instance;
// Step 2: Private constructor to prevent instantiation from other classes.
private Singleton() {}
// Step 3: Public method to provide access to the single instance.
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
// Example method in Singleton class
public void displayMessage() {
System.out.println(“This is a Singleton class!”);
}
}
public class Main
{
public static void main(String[] args)
{
Singleton singleton = Singleton.getInstance();
singleton.displayMessage();
}
}
In above code,
Private Constructor: The constructor is private to prevent the class from being instantiated from outside.
Static Instance: A static reference to the instance ensures that it is shared across all references to the class.
Double-Checked Locking: The getInstance() method uses a synchronized block inside the if check to ensure that only one instance is created, even in multi-threaded environments. The check is done twice to reduce overhead.
In a multithreaded environment, the synchronized block ensures that the instance is created only once. This method avoids the performance overhead of synchronization after the instance is created, making it thread-safe and efficient
2. Factory Design Pattern
A Java factory design pattern helps in creating instances/objects for your classes.
As the name signifies Factory, it is a place where different products are created that are similar in features yet divided into categories
Mostly we can create objects by using the “New” keyword. butthe Java factory pattern is a better way to create instances of the class
The creation of an object is not exposed to the client and the client uses the same common interface to create a new type of object.
The core idea behind the static factory method is to create and return instances wherein the details of the class module are hidden from the user.
This pattern promotes loose coupling, as the client code doesn’t need to know about the specific types of objects that it is creating.
A typical scenario might involve creating a set of related objects, but the client doesn’t know exactly which object it needs ahead of time. For example, you could create different types of database connections or payment methods. The Factory pattern allows you to separate the object creation logic from the client code, leading to better scalability and flexibility.
Use Case:
When a class cannot anticipate the type of objects it needs to create.When the client code should not be responsible for creating objects.When you want to encapsulate the creation logic of objects in one place.
Program:
// Product interface
public interface Product {
void create();
}
// Concrete Product A
public class ProductA implements Product {
@Override
public void create() {
System.out.println(“ProductA Created!”);
}
}
// Concrete Product B
public class ProductB implements Product {
@Override
public void create() {
System.out.println(“ProductB Created!”);
}
}
// Creator class (Factory)
public abstract class Creator {
public abstract Product factoryMethod();
}
// Concrete Creator A
public class CreatorA extends Creator {
@Override
public Product factoryMethod() {
return new ProductA();
}
}
// Concrete Creator B
public class CreatorB extends Creator {
@Override
public Product factoryMethod() {
return new ProductB();
}
}
public class Main {
public static void main(String[] args) {
Creator creatorA = new CreatorA();
Product productA = creatorA.factoryMethod();
productA.create();
Creator creatorB = new CreatorB();
Product productB = creatorB.factoryMethod();
productB.create();
}
}
In above code,
Product Interface: Defines a contract that all product types (concrete classes) must implement.
Concrete Products: These are the specific implementations of the Product interface.
Creator Class: An abstract class that declares the factory method, which returns a product. The concrete subclasses implement this method to return the appropriate product.
Concrete Creators: These are subclasses of the Creator class that implement the factoryMethod() to instantiate and return different Product types.
Advantages:
- Decoupling Object Creation: The client doesn’t need to know the specific class of the object being created, which promotes loose coupling.
- Extensibility: You can easily add new products by creating new concrete classes without modifying the client code.
Figure 2: Factory Design Pattern
3. Facade Design Pattern
The Facade Design Pattern is a structural pattern that provides a simplified interface to a complex system of classes, making it easier for clients to use. Imagine you have a system with many different classes, each responsible for a specific task. Without a facade, the client would need to interact with each of these classes directly, which can be complicated and confusing.
The Facade acts as a “front door” to the system, offering a single interface to interact with instead of dealing with the complexities behind the scenes. It hides the complex details and provides a much simpler way for the client to access the functionality.
For example, think about how you interact with a TV. You don’t need to know how the TV’s internal components (such as the circuit board, wires, etc.) work. Instead, you use a remote control — which is the Facade. The remote has simple buttons like “power on” or “volume up,” and you don’t need to understand how pressing these buttons interacts with the internal components of the TV.
The Facade Design Pattern is useful when you want to simplify the interaction with a complex system. It hides the complexity by providing a simplified interface, making the system easier to use and understand.
Program:
// Subsystems
class SubsystemA {
public void operationA() {
System.out.println(“Operation A in Subsystem A”);
}
}
class SubsystemB {
public void operationB() {
System.out.println(“Operation B in Subsystem B”);
}
}
class SubsystemC {
public void operationC() {
System.out.println(“Operation C in Subsystem C”);
}
}
// Facade Class
public class Facade {
private SubsystemA subsystemA;
private SubsystemB subsystemB;
private SubsystemC subsystemC;
public Facade() {
subsystemA = new SubsystemA();
subsystemB = new SubsystemB();
subsystemC = new SubsystemC();
}
public void simplifiedOperation() {
subsystemA.operationA();
subsystemB.operationB();
subsystemC.operationC();
}
}
public class Main {
public static void main(String[] args) {
Facade facade = new Facade();
facade.simplifiedOperation(); // The client uses this single method to interact with multiple subsystems
}
}
In above code,
Subsystems: We have three different subsystems (A, B, and C), each performing its own specific task.
Facade Class: The Facade class provides a method called simplifiedOperation() that interacts with all these subsystems behind the scenes.
Client: Instead of interacting with each subsystem directly, the client only calls the simplifiedOperation() method in the facade.
Note: Do watch our latest video: Click Here
Author:-
Pooja Nandode-Bhavsar
Call the Trainer and Book your free demo class Python for now!!!
© Copyright 2020 | SevenMentor Pvt Ltd.