
Design Patterns in Java

Table of Contnets
- What is a Design Pattern?
- Why Use Design Patterns?
- Singleton
- Factory Method
- Abstract Factory
- Builder
- Prototype
- Clean Code
- Adapter
- Observer
- Factory Method pattern
- Conclusion
Hello, fellow code crafters!
As I ventured deeper into the world of Java, I stumbled upon the elegant realms of best practices and design patterns.
These concepts are not just rules or templates; they are the distilled essence of decades of collective experience from expert developers. They guide us to build more maintainable, scalable, and efficient software.
Let’s embark on this enlightening journey together, and I’ll share how embracing best practices and design patterns has reshaped my coding perspective.
What is a Design Pattern?
A design pattern is a general, reusable solution to a commonly occurring problem within a given context in software design. It is not a finished design that can be transformed directly into code; rather, it is a template for how to solve a problem that can be used in many different situations. Design patterns are formalized best practices that a programmer can use to solve common problems when designing an application or system.
Read more: Scenario Based Java Interview Questions
Why Use Design Patterns?
Design patterns provide solutions to common software design issues that are tested and proven over time, thus improving code readability and reliability. By using these patterns, developers can avoid reinventing solutions to known problems, which increases the efficiency of the development process and ensures greater consistency, and maintenance ease. Moreover, design patterns are often used to communicate complex ideas in a simpler manner, making them invaluable in collaborative team environments where consistency and clarity of design are paramount.
Enhance your Java skills with our experienced trainers who are ready to guide you into becoming a seasoned Java professional with our Java training. Dive into hands-on, real-time projects that prepare you for real-world programming challenges. Enroll for a free demo today and start your journey towards Java expertise!
Singleton
The Singleton design pattern ensures that a class has only one instance and provides a global point of access to it. This is particularly useful when exactly one object is needed to coordinate actions across the system. For example, you might use a Singleton for a configuration manager in an application. The manager reads configuration settings from a file and provides global access to these settings throughout the application. Implementing this manager as a Singleton ensures that the configuration is loaded only once and the same instance is used throughout, preventing inconsistencies.
Factory Method
The Factory Method pattern defines an interface for creating an object, but lets subclasses decide which class to instantiate. This pattern is used when a class cannot anticipate the class of objects it needs to create beforehand. An example of this pattern would be a logistics management application that can handle different types of transport vehicles, like trucks or ships. A Transport
base class would define an interface, and subclasses Truck
and Ship
would implement this interface. A TransportFactory
interface would allow a subclass to instantiate Truck
or Ship
objects, thus delegating instantiation to the subclasses based on the logistics requirement.
Abstract Factory
The Abstract Factory pattern provides an interface for creating families of related or dependent objects without specifying their concrete classes. This pattern is ideal when there are interdependencies among the products that can be created. Consider a user interface toolkit that needs to create elements like buttons, checkboxes, and text fields that are visually consistent across different operating systems. An AbstractFactory
interface would provide methods like createButton
, createCheckbox
, and createTextField
. Subclasses like WindowsFactory
and MacOSFactory
would implement these methods to instantiate products specific to each operating system, ensuring that all UI elements are consistent with the OS theme.
Read more:Â Accenture Java Interview Questions and Answers
Builder
The Builder pattern separates the construction of a complex object from its representation, allowing the same construction process to create different representations. This pattern is used to construct complex objects step by step. An example would be a meal builder in a fast-food restaurant application. A MealBuilder
interface defines step-by-step methods to add parts like burgers, fries, and drinks. Concrete builders like VegMealBuilder
and NonVegMealBuilder
implement this interface to offer different types of meals. This pattern allows the director to construct complex meal objects using a uniform construction process.
class House {
private String foundation, structure, roof;
private boolean furnished;
private House(HouseBuilder builder) { /*...*/ }
public static class HouseBuilder {
private String foundation, structure, roof;
private boolean furnished;
public HouseBuilder setFoundation(String f) { this.foundation = f; return this; }
public HouseBuilder setStructure(String s) { this.structure = s; return this; }
public HouseBuilder setRoof(String r) { this.roof = r; return this; }
public HouseBuilder furnish(boolean f) { this.furnished = f; return this; }
public House build() { return new House(this); }
}
}
The Builder Pattern helps construct complex objects step-by-step by separating the construction process from the final object (House
). The HouseBuilder
class has methods for setting parts of the House
(e.g., setFoundation()
, setStructure()
), and the build()
method finalizes the object creation. This pattern provides flexibility, allowing clients to create objects by chaining only the needed steps, ensuring controlled and consistent object construction.
Read more:Â TCS Java Interview Questions
Prototype
The Prototype pattern specifies the kinds of objects to create using a prototypical instance and creates new objects by copying this prototype. This pattern is used when creating a new object directly is expensive or complicated. For instance, in a game, you might use the Prototype pattern for spawning identical monsters with the same initial state or behavior. A Monster
prototype object holds the default state (health, speed, attack type). When a new monster needs to be created, the prototype simply clones itself, thereby avoiding the cost of creating a new monster from scratch each time.
class Car implements Cloneable {
String model;
public Car(String model) { this.model = model; }
public Car clone() throws CloneNotSupportedException { return (Car) super.clone(); }
}
public class PrototypeExample {
public static void main(String[] args) throws CloneNotSupportedException {
Car car1 = new Car("Sedan");
Car car2 = car1.clone(); // Creates a clone of car1
}
}
The Prototype Pattern allows creating objects by copying an existing object, known as a prototype, rather than creating new instances from scratch. In Java, it leverages the Cloneable
interface and the clone()
method. This approach helps when object creation is resource-intensive or when you need multiple instances with the same state. In this example, Car
can be cloned easily, saving effort in re-creating objects with similar properties.
Read more:Â List Class in Salesforce Apex
Best Practices
Writing clean code is like keeping your workspace tidy; it not only reflects professionalism but also makes life easier for you and your colleagues in the long run.
Meaningful Names:
Choosing intuitive and descriptive names for variables, methods, and classes makes your code self-explanatory and reduces the need for additional comments.
// Instead of this:
int d; // elapsed time in days
// Opt for:
int elapsedTimeInDays;
SOLID Principles:
SOLID stands for five design principles intended to make software designs more understandable, flexible, and maintainable.
Singleton
The Singleton Pattern is a creational design pattern that restricts a class to have only one instance while providing a global access point to that instance. This is particularly useful when exactly one object is needed to coordinate actions across the system, such as in configurations, logging, or connection pooling.
public class Singleton {
private static Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
Adapter
The Adapter Pattern is a structural design pattern that allows incompatible interfaces to work together. It acts as a bridge between two different classes by converting the interface of a class into another interface that the client expects. In Java, the Adapter pattern can be used when we need to integrate a class with an existing interface, without modifying the class itself. This pattern is helpful when you want to reuse existing classes in a new environment where the interfaces differ.
public interface LightningPhone {
void recharge();
void useLightning();
}
public interface MicroUsbPhone {
void recharge();
void useMicroUsb();
}
public class LightningToMicroUsbAdapter implements MicroUsbPhone {
private final LightningPhone lightningPhone;
public LightningToMicroUsbAdapter(LightningPhone lightningPhone) {
this.lightningPhone = lightningPhone;
}
@Override
public void recharge() {
lightningPhone.recharge();
}
@Override
public void useMicroUsb() {
System.out.println("MicroUsb connected");
lightningPhone.useLightning();
}
}
Observer
Observer is an object that monitors changes in another object, called the Subject. When the subject’s state changes, it automatically notifies all its observers. Each observer implements an update()
method, which defines how it reacts to changes. This pattern promotes loose coupling, as the subject doesn’t need to know the specifics of each observer; it only needs to notify them when changes occur. This is commonly used in event handling systems or in any situation where changes in one object need to trigger actions in others.
import java.util.ArrayList;
import java.util.List;
interface Observer {
void update(String message);
}
class Subject {
private List<Observer> observers = new ArrayList<>();
public void addObserver(Observer o) { observers.add(o); }
public void notifyObservers(String msg) {
for (Observer o : observers) o.update(msg);
}
}
class ConcreteObserver implements Observer {
public void update(String message) { System.out.println("Received: " + message); }
}
The Observer Pattern allows objects (Observers) to get automatically notified when the state of another object (Subject) changes. Subject
maintains a list of observers, and when a change occurs, it calls notifyObservers()
to inform them. Observers implement the update()
method to react to notifications. This pattern is useful when multiple objects need to be in sync with a subject’s state changes, promoting loose coupling.
Factory Method pattern:
- Define a Common Interface: Ensure all products share a common interface, which allows the client code to interact with the products through a base type or interface.
- Encapsulate Object Creation: Use factory methods to encapsulate the creation of objects. This isolation helps in managing dependencies and supports the Open/Closed Principle, making the application easier to extend.
- Defer Instantiation: Let subclasses decide exactly what objects to create. The factory method in the base class should return a product interface, while subclasses override this method to instantiate specific products.
- Promote Loose Coupling: The pattern promotes loose coupling between the creator and the concrete products. The creator doesn’t need to know the concrete classes of the products it creates, which enhances modularity and flexibility.
Conclusion:
My exploration into best practices and design patterns in Java has been nothing short of transformative. It’s like discovering a treasure trove of wisdom that guides you to craft not just functional but elegant and robust software. Embracing these practices and patterns doesn’t just make your code better; it elevates your journey as a developer. I encourage you to delve into these concepts, understand the underlying principles, and watch as they turn your code into a masterpiece of efficiency and clarity. Happy coding!
Through this blog post, we journeyed through the realms of best practices and design patterns, uncovering the secrets to writing excellent, maintainable, and efficient Java code. These concepts are your allies in the quest to become not just a coder, but a craftsman of software, molding your creations with precision, insight, and artistry. Keep learning, keep coding, and let the principles of best practices and design patterns guide your path to excellence!