Software Architecture Design Patterns in Java
1. Singleton Pattern
Purpose: The Singleton pattern ensures that a class has only one instance and provides a global point of access to that instance.
Benefits:
- Controls access to a single instance.
- Reduces overhead by reusing the same instance.
Example:
javapublic class Singleton { private static Singleton instance; private Singleton() {} public static synchronized Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } }
In this example, the Singleton
class has a private constructor to prevent direct instantiation. The getInstance()
method provides a global access point and ensures that only one instance is created.
2. Factory Method Pattern
Purpose: The Factory Method pattern defines an interface for creating objects but allows subclasses to alter the type of objects that will be created.
Benefits:
- Promotes loose coupling.
- Allows for more flexible and maintainable code.
Example:
javapublic abstract class Product { public abstract void use(); } public class ConcreteProductA extends Product { @Override public void use() { System.out.println("Using Product A"); } } public class ConcreteProductB extends Product { @Override public void use() { System.out.println("Using Product B"); } } public abstract class Creator { public abstract Product factoryMethod(); } public class ConcreteCreatorA extends Creator { @Override public Product factoryMethod() { return new ConcreteProductA(); } } public class ConcreteCreatorB extends Creator { @Override public Product factoryMethod() { return new ConcreteProductB(); } }
Here, the Creator
class defines a factoryMethod()
that is implemented by concrete creators to produce different types of products.
3. Observer Pattern
Purpose: The Observer pattern allows an object (subject) to notify other objects (observers) of changes in its state.
Benefits:
- Supports dynamic relationships between objects.
- Promotes low coupling between components.
Example:
javaimport java.util.ArrayList; import java.util.List; public interface Observer { void update(String message); } public class ConcreteObserver implements Observer { private String name; public ConcreteObserver(String name) { this.name = name; } @Override public void update(String message) { System.out.println(name + " received: " + message); } } public class Subject { private List
observers = new ArrayList<>(); public void addObserver(Observer observer) { observers.add(observer); } public void notifyObservers(String message) { for (Observer observer : observers) { observer.update(message); } } }
In this pattern, the Subject
class maintains a list of observers and notifies them of changes. Observers implement the Observer
interface to receive updates.
4. Strategy Pattern
Purpose: The Strategy pattern defines a family of algorithms, encapsulates each algorithm, and makes them interchangeable.
Benefits:
- Promotes the open/closed principle.
- Allows changing algorithms or behaviors dynamically.
Example:
javapublic interface Strategy { int execute(int a, int b); } public class AdditionStrategy implements Strategy { @Override public int execute(int a, int b) { return a + b; } } public class SubtractionStrategy implements Strategy { @Override public int execute(int a, int b) { return a - b; } } public class Context { private Strategy strategy; public Context(Strategy strategy) { this.strategy = strategy; } public int executeStrategy(int a, int b) { return strategy.execute(a, b); } }
In the Strategy
pattern, the Context
class uses a Strategy
to perform a particular operation. Different strategies (e.g., addition or subtraction) can be passed at runtime.
5. Decorator Pattern
Purpose: The Decorator pattern allows adding new functionalities to objects dynamically without altering their structure.
Benefits:
- Enhances flexibility and extensibility.
- Supports the single responsibility principle.
Example:
javapublic interface Coffee { String getDescription(); double cost(); } public class SimpleCoffee implements Coffee { @Override public String getDescription() { return "Simple coffee"; } @Override public double cost() { return 5.0; } } public abstract class CoffeeDecorator implements Coffee { protected Coffee decoratedCoffee; public CoffeeDecorator(Coffee decoratedCoffee) { this.decoratedCoffee = decoratedCoffee; } @Override public String getDescription() { return decoratedCoffee.getDescription(); } @Override public double cost() { return decoratedCoffee.cost(); } } public class MilkDecorator extends CoffeeDecorator { public MilkDecorator(Coffee decoratedCoffee) { super(decoratedCoffee); } @Override public String getDescription() { return decoratedCoffee.getDescription() + ", milk"; } @Override public double cost() { return decoratedCoffee.cost() + 2.0; } }
The Decorator
pattern allows for dynamically adding functionalities such as milk to the coffee without changing the core SimpleCoffee
class.
Conclusion
Java design patterns provide robust solutions to common design problems and help in creating well-structured and maintainable code. By understanding and applying these patterns, developers can improve their ability to design effective software architectures. Whether it's managing object creation with the Singleton pattern, improving flexibility with the Factory Method, or enhancing dynamic behavior with the Observer and Strategy patterns, these design patterns offer valuable techniques for building high-quality Java applications.
Popular Comments
No Comments Yet