Java design patterns are best practices used to solve common
software design problems. They provide a way to organize code to make it more
efficient, maintainable, and scalable. Here’s an overview of some common design
patterns in Java:
1. Creational Patterns
Ø Singleton:
Ensures a class has only one instance and provides a global point of access to
it.
Ø Factory
Method: Creates objects without specifying the exact class of object that
will be created. Defines an interface for creating an object, but lets
subclasses alter the type of objects that will be created.
Ø Abstract
Factory: Provides an interface for creating families of related or
dependent objects without specifying their concrete classes.
Ø Builder:
Separates the construction of a complex object from its representation so that
the same construction process can create different representations.
Ø Prototype:
Creates new objects by copying an existing object, known as a prototype.
2. Structural Patterns
Ø Adapter:
Allows incompatible interfaces to work together by wrapping an existing class
with a new interface.
Ø Decorator:
Adds new functionality to an object dynamically without altering its structure.
Ø Facade:
Provides a simplified interface to a complex subsystem.
Ø Proxy:
Provides a surrogate or placeholder for another object to control access to it.
Ø Composite:
Composes objects into tree structures to represent part-whole hierarchies. It
lets clients treat individual objects and compositions uniformly.
Ø Bridge
Decouples an abstraction from its implementation so that the two can vary
independently.
Ø Flyweight
Reduces the cost of creating and manipulating a large number of similar objects
by sharing objects where possible.
3. Behavioral Patterns
Ø Strategy:
Defines a family of algorithms, encapsulates each one, and makes them
interchangeable. The strategy pattern lets the algorithm vary independently
from clients that use it.
Ø Observer:
Defines a one-to-many dependency between objects so that when one object
changes state, all its dependents are notified and updated automatically.
Ø Command:
Encapsulates a request as an object, thereby allowing for parameterization of
clients with queues, requests, and operations.
Ø State:
Allows an object to alter its behavior when its internal state changes. The
object will appear to change its class.
Ø Template
Method: Defines the skeleton of an algorithm in the superclass but lets
subclasses override specific steps of the algorithm without changing its
structure.
Ø Chain
of Responsibility: Passes a request along a chain of handlers, allowing
each handler to process the request or pass it to the next handler.
Ø Interpreter:
Defines a grammatical representation for a language and provides an interpreter
to interpret sentences in the language. It is useful when you want to evaluate
expressions in a language or when the grammar is simple and well-understood
Ø Iterator:
Provides a way to access the elements of an aggregate object sequentially
without exposing its underlying representation.
Ø Mediator:
Defines an object that encapsulates how a set of objects interact, promoting
loose coupling by keeping objects from referring to each other explicitly.
Ø Memento:
Captures and externalizes an object's internal state so that it can be restored
later, without violating encapsulation.
Ø Visitor:
Represents an operation to be performed on the elements of an object structure,
allowing you to define a new operation without changing the classes of the
elements on which it operates.
4. Concurrency Patterns
Ø Thread
Pool: Manages a pool of worker threads to efficiently execute tasks in
parallel.
Ø Future:
Represents the result of an asynchronous computation, providing methods to
check if the computation is complete, to wait for its completion, and to
retrieve the result.
Ø Producer-Consumer:
Separates the creation of tasks (producer) from the execution of tasks
(consumer) using a queue.
Ø Read-Write
Lock: Allows multiple threads to read a shared resource concurrently but
requires exclusive access for writes, preventing conflicts.
5. Architectural Patterns
These patterns provide general solutions for software
architecture problems.
Ø Model-View-Controller
(MVC)
- Separates an
application into three interconnected components: the Model (data), the
View (UI), and the Controller (logic), allowing separation of concerns
and independent development of components.
Ø
Model-View-ViewModel (MVVM)
·
Similar to MVC, but adds a ViewModel to handle
the presentation logic, binding the View and the Model.
Ø
Data Access Object (DAO)
- Provides an abstract
interface to a database or other persistence mechanism, separating the
persistence logic from the business logic.
6. Other Patterns
Ø
Null Object
·
Provides a default behavior when an object is
absent or null, avoiding null references.
Ø
Service Locator
·
Provides a centralized registry for services,
making it easy to find and use service instances.
Creating a hierarchical overview of Java design patterns
Creating a hierarchical overview of Java design patterns
using a flowchart along with corresponding Java examples involves organizing
the patterns into categories and illustrating their relationships in a
flowchart format. Below is a conceptual flowchart representation along with
simplified Java code examples for each pattern category.
Flowchart: Hierarchical Overview of Java Design Patterns
Hierarchical Overview of Java Design Patterns |
Java Code Examples
1. Creational Patterns
a. Singleton Pattern
java
public class
Singleton { private static Singleton instance; private Singleton() { // private constructor to prevent
instantiation } public static Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } } |
b. Factory Method Pattern
java
interface
Product { void use(); } class
ConcreteProductA implements Product { public void use() { System.out.println("Using
Product A"); } } class
ConcreteProductB implements Product { public void use() { System.out.println("Using
Product B"); } } abstract
class Creator { public abstract Product factoryMethod(); public void someOperation() { Product product = factoryMethod(); product.use(); } } class
ConcreteCreatorA extends Creator { public Product factoryMethod() { return new ConcreteProductA(); } } class
ConcreteCreatorB extends Creator { public Product factoryMethod() { return new ConcreteProductB(); } } |
c. Abstract Factory Pattern
java
interface
AbstractFactory { ProductA createProductA(); ProductB createProductB(); } class
ConcreteFactory1 implements AbstractFactory { public ProductA createProductA() { return new ProductA1(); } public ProductB createProductB() { return new ProductB1(); } } class
ConcreteFactory2 implements AbstractFactory { public ProductA createProductA() { return new ProductA2(); } public ProductB createProductB() { return new ProductB2(); } } interface
ProductA { void use(); } class
ProductA1 implements ProductA { public void use() { System.out.println("Using
Product A1"); } } class
ProductA2 implements ProductA { public void use() { System.out.println("Using
Product A2"); } } interface
ProductB { void use(); } class
ProductB1 implements ProductB { public void use() { System.out.println("Using
Product B1"); } } class
ProductB2 implements ProductB { public void use() { System.out.println("Using
Product B2"); } } |
2. Structural Patterns
a. Adapter Pattern
java
interface
Target { void request(); } class Adaptee
{ void specificRequest() { System.out.println("Specific
request"); } } class Adapter
implements Target { private Adaptee adaptee; public Adapter(Adaptee adaptee) { this.adaptee = adaptee; } public void request() { adaptee.specificRequest(); } } |
b. Decorator Pattern
java
interface
Component { void operation(); } class
ConcreteComponent implements Component { public void operation() { System.out.println("Operation in
ConcreteComponent"); } } abstract
class Decorator implements Component { protected Component component; public Decorator(Component component) { this.component = component; } public void operation() { component.operation(); } } class
ConcreteDecorator extends Decorator { public ConcreteDecorator(Component
component) { super(component); } public void operation() { super.operation(); System.out.println("Added
behavior in ConcreteDecorator"); } } |
c. Composite Pattern
java
import
java.util.ArrayList; import
java.util.List; interface
Component { void operation(); } class Leaf
implements Component { public void operation() { System.out.println("Operation in
Leaf"); } } class
Composite implements Component { private List<Component> children =
new ArrayList<>(); public void add(Component component) { children.add(component); } public void remove(Component component) { children.remove(component); } public void operation() { for (Component child : children) { child.operation(); } } } |
3. Behavioral Patterns
a. Strategy Pattern
java
interface
Strategy { void execute(); } class
ConcreteStrategyA implements Strategy { public void execute() { System.out.println("Executing
Strategy A"); } } class
ConcreteStrategyB implements Strategy { public void execute() { System.out.println("Executing
Strategy B"); } } class Context
{ private Strategy strategy; public void setStrategy(Strategy
strategy) { this.strategy = strategy; } public void executeStrategy() { strategy.execute(); } } |
b. Observer Pattern
java
import
java.util.ArrayList; import
java.util.List; interface
Observer { void update(); } class
ConcreteObserver implements Observer { public void update() { System.out.println("Observer
updated"); } } class Subject
{ private List<Observer> observers =
new ArrayList<>(); public void attach(Observer observer) { observers.add(observer); } public void detach(Observer observer) { observers.remove(observer); } public void notifyObservers() { for (Observer observer : observers) { observer.update(); } } } |
c. Command Pattern
java
interface
Command { void execute(); } class
ConcreteCommand implements Command { private Receiver receiver; public ConcreteCommand(Receiver receiver)
{ this.receiver = receiver; } public void execute() { receiver.action(); } } class
Receiver { public void action() { System.out.println("Action
executed"); } } class Invoker
{ private Command command; public void setCommand(Command command) { this.command = command; } public void executeCommand() { command.execute(); } } |
d. Chain of Responsibility
java
A> abstract
class Logger { public static int DEBUG = 1; public static int INFO = 2; public static int ERROR = 3; protected int level; // Next element in the chain of
responsibility protected Logger nextLogger; public void setNextLogger(Logger
nextLogger) { this.nextLogger = nextLogger; } public void logMessage(int level, String
message) { if (this.level <= level) { write(message); } if (nextLogger != null) { nextLogger.logMessage(level,
message); } } protected abstract void write(String
message); } B> class
DebugLogger extends Logger { public DebugLogger(int level) { this.level = level; } @Override protected void write(String message) { System.out.println("DEBUG
Logger: " + message); } } class
InfoLogger extends Logger { public InfoLogger(int level) { this.level = level; } @Override protected void write(String message) { System.out.println("INFO Logger:
" + message); } } class
ErrorLogger extends Logger { public ErrorLogger(int level) { this.level = level; } @Override protected void write(String message) { System.out.println("ERROR
Logger: " + message); } } C> class
ChainPatternDemo { private static Logger getChainOfLoggers()
{ Logger errorLogger = new
ErrorLogger(Logger.ERROR); Logger infoLogger = new
InfoLogger(Logger.INFO); Logger debugLogger = new
DebugLogger(Logger.DEBUG); // Setting up the chain: DebugLogger
-> InfoLogger -> ErrorLogger
debugLogger.setNextLogger(infoLogger);
infoLogger.setNextLogger(errorLogger); return debugLogger; } public static void main(String[] args) { Logger loggerChain =
getChainOfLoggers(); loggerChain.logMessage(Logger.DEBUG,
"This is a debug level information."); loggerChain.logMessage(Logger.INFO,
"This is an info level information."); loggerChain.logMessage(Logger.ERROR,
"This is an error level information."); } } Explanation
|
e. interpreter
A> interface
Expression { int interpret(); } B> class
NumberExpression implements Expression { private int number; public NumberExpression(int number) { this.number = number; } public NumberExpression(String number) { this.number =
Integer.parseInt(number); } @Override public int interpret() { return this.number; } } C> class
AddExpression implements Expression { private Expression leftExpression; private Expression rightExpression; public AddExpression(Expression
leftExpression, Expression rightExpression) { this.leftExpression = leftExpression; this.rightExpression =
rightExpression; } @Override public int interpret() { return leftExpression.interpret() +
rightExpression.interpret(); } } D> class
SubtractExpression implements Expression { private Expression leftExpression; private Expression rightExpression; public SubtractExpression(Expression
leftExpression, Expression rightExpression) { this.leftExpression = leftExpression; this.rightExpression =
rightExpression; } @Override public int interpret() { return leftExpression.interpret() -
rightExpression.interpret(); } } E> import
java.util.Stack; class
InterpreterClient { public static Expression parse(String
expression) { Stack<Expression> stack = new
Stack<>(); String[] tokens =
expression.split(" "); for (String token : tokens) { if (isOperator(token)) { Expression rightExpression =
stack.pop(); Expression leftExpression =
stack.pop(); Expression operator =
getOperator(token, leftExpression, rightExpression); int result =
operator.interpret(); stack.push(new
NumberExpression(result)); } else { stack.push(new
NumberExpression(token)); } } return stack.pop(); } private static boolean isOperator(String
token) { return token.equals("+") ||
token.equals("-"); } private static Expression
getOperator(String token, Expression left, Expression right) { switch (token) { case "+": return new
AddExpression(left, right); case "-": return new
SubtractExpression(left, right); default: throw new
UnsupportedOperationException("Unknown operator: " + token); } } } F> public class
InterpreterPatternDemo { public static void main(String[] args) { String expression = "7 3 - 2 1 +
+"; // (7 - 3) + (2 + 1) Expression result =
InterpreterClient.parse(expression); System.out.println(expression +
" = " + result.interpret()); } } G: output 7 3 - 2 1 + +
= 7 Explanation
Use Case
|
4. Concurrency Patterns
a. Thread Pool Pattern
java
import
java.util.concurrent.ExecutorService; import
java.util.concurrent.Executors; class Task
implements Runnable { public void run() { System.out.println("Task
executed by " + Thread.currentThread().getName()); } } public class
ThreadPoolExample { public static void main(String[] args) { ExecutorService executor =
Executors.newFixedThreadPool(5); for (int i = 0; i < 10; i++) { executor.execute(new Task()); } executor.shutdown(); } } |
b. Producer-Consumer Pattern
java
import
java.util.concurrent.ExecutorService; import
java.util.concurrent.Executors; class Task
implements Runnable { public void run() { System.out.println("Task
executed by " + Thread.currentThread().getName()); } } public class
ThreadPoolExample { public static void main(String[] args) { ExecutorService executor =
Executors.newFixedThreadPool(5); for (int i = 0; i < 10; i++) { executor.execute(new Task()); } executor.shutdown(); } } |
c. Future Pattern
java
import
java.util.concurrent.Callable; import
java.util.concurrent.ExecutionException; import
java.util.concurrent.ExecutorService; import
java.util.concurrent.Executors; import
java.util.concurrent.Future; class Task
implements Callable<String> { public String call() throws Exception { Thread.sleep(2000); return "Task completed"; } } public class
FutureExample { public static void main(String[] args) { ExecutorService executor =
Executors.newFixedThreadPool(2); Future<String> future =
executor.submit(new Task()); try { System.out.println("Result:
" + future.get()); } catch (InterruptedException |
ExecutionException e) { e.printStackTrace(); } executor.shutdown(); } } |
Difference between Java Design Patterns and SOLID
principles
Java Design Patterns and SOLID principles are both essential
concepts in software design, but they serve different purposes and operate at
different levels of abstraction. Here’s a breakdown of their differences:
1. Purpose
- Java
Design Patterns:
- Design
patterns are typical solutions to common problems in software design.
They are proven templates that can be adapted to solve specific design
issues in your code.
- Examples
include Singleton, Factory Method, Observer, and Decorator.
- Design
patterns provide solutions to recurring problems and guide the structure
and interaction of classes and objects.
- SOLID
Principles:
- SOLID
principles are a set of guidelines for writing clean, maintainable, and
scalable code. They help in ensuring that the code is flexible, easy to
understand, and less prone to bugs.
- SOLID
stands for:
- Single
Responsibility Principle (SRP)
- Open/Closed
Principle (OCP)
- Liskov
Substitution Principle (LSP)
- Interface
Segregation Principle (ISP)
- Dependency
Inversion Principle (DIP)
- These
principles focus on designing individual classes and systems in a way
that they are more robust and easier to manage.
2. Level of Abstraction
- Java
Design Patterns:
- Operate
at a higher level of abstraction.
- They
are more about the architecture or structure of an entire system or a
subsystem.
- Design
patterns describe how classes and objects interact to solve a specific
problem in a certain context.
- SOLID
Principles:
- Operate
at a lower level of abstraction.
- They
guide the design of individual classes and their interactions.
- SOLID
principles are about writing good class-level code that is easy to
maintain and extend.
3. Scope
- Java
Design Patterns:
- Applied
to solve specific problems within a given scope, such as creating objects
(Factory Method), managing object behavior (Observer), or structuring
classes (Decorator).
- Design
patterns can be applied to various parts of the application, depending on
where the design issue occurs.
- SOLID
Principles:
- Applied
universally across all parts of the codebase to ensure the code is
flexible, modular, and easily understandable.
- SOLID
principles are the foundation for good object-oriented design practices
and are not tied to specific problems but are general principles to
follow.
4. Reusability
- Java
Design Patterns:
- Provide
reusable solutions to recurring design problems.
- Patterns
can be reused across different projects or systems whenever similar
design problems arise.
- SOLID
Principles:
- Ensure
that individual components of the system are reusable.
- By
following SOLID principles, the classes you write are more likely to be
reusable in different contexts because they adhere to good design
practices.
5. Example Comparison
- Single
Responsibility Principle (SRP) from SOLID:
- A
class should have only one reason to change, meaning it should have only
one job or responsibility.
- For
instance, if you have a class responsible for both data validation and
saving data to the database, SRP would suggest splitting these
responsibilities into two separate classes.
- Factory
Method Pattern:
- Provides
an interface for creating objects in a super class, but allows subclasses
to alter the type of objects that will be created.
- The
Factory Method pattern can be implemented in a way that adheres to the
Open/Closed Principle (OCP), another SOLID principle, by allowing the
creation of objects to be extended without modifying existing code.
6. Interrelation
- Java
Design Patterns and SOLID Principles:
- Design
patterns often embody SOLID principles. For example, the Strategy
Pattern aligns with the Open/Closed Principle by allowing behavior to
be chosen at runtime without modifying existing code.
- Implementing
design patterns can help you adhere to SOLID principles, but it is also
possible to misuse them in ways that violate SOLID principles.
Summary
ü Java
Design Patterns provide solutions to specific design problems by defining
the structure and interaction between classes and objects.
ü SOLID
Principles guide the design of individual classes and their interactions to
ensure the codebase is robust, maintainable, and scalable.
ü They
complement each other: design patterns often implement SOLID principles, and
adhering to SOLID principles makes it easier to implement design patterns
effectively.
0 Comments