What are Java Design Patterns?
Java design patterns are the cornerstone of building scalable, maintainable, and efficient software solutions.
They encapsulate best practice, and proven solutions to recurring design problems, and promote code reusability and flexibility.
Whether you’re a fresher or an experienced Java developer, understanding and implementing design patterns can significantly elevate your programming skills. In this blog, we’ll explore the fundamentals of Java design patterns, their classification, and practical examples to demonstrate their usage in real-world scenarios.
Understanding Java Design Patterns:
- Java design patterns are reusable solutions to common software design problems encountered during development.
- They provide a structured approach to designing software systems, promoting modularity, extensibility, and maintainability.
- Design patterns abstract away specific implementation details, allowing developers to focus on high-level design principles and architecture.
Classification of Java Design Patterns:
Java design patterns are classified into three main categories based on their purpose and scope:
- Creational Patterns:
- Creational patterns focus on object creation mechanisms, providing flexible ways to create objects while hiding the creation logic from clients.
- Examples include
- Singleton Pattern
- Factory Pattern
- Abstract Factory Pattern
- Builder Pattern
- Prototype Pattern
- Structural Patterns:
- Structural patterns deal with object composition, defining how classes and objects are organized to form larger structures.
- They facilitate building complex systems by identifying simple ways to realize relationships between entities.
- Examples include
- Adapter
- Decorator
- Proxy
- Composite
- Bridge.
- Behavioral Patterns:
- Behavioral patterns focus on communication between objects and the assignment of responsibilities.
- They define how objects interact with each other and encapsulate algorithms and behaviors.
- Examples include
- Observer
- Strategy
- Command
- Template Method
- State
Let’s see examples for each pattern:
Creational Patterns
Singleton Pattern:
- The Singleton pattern ensures that a class has only one instance and provides a global point of access to that instance.
- This pattern is useful when exactly one object is needed to coordinate actions across the system.
- Following is the example for the Java singleton class
public static void main(String[] args) {
// Get the instance of Singleton class
Singleton singleton1 = Singleton.getInstance();
Singleton singleton2 = Singleton.getInstance();
// Both references point to the same object
System.out.println(singleton1 == singleton2); // Output: true
}
}
class Singleton {
// Private static variable to hold the single instance of the class
private static Singleton instance;
// Private constructor to prevent instantiation from outside the class
private Singleton() {
}
// Public static method to get the single instance of the class
public static Singleton getInstance() {
// Lazy initialization: create the instance only if it doesn’t exist yet
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
In the above implementation:
- We have a private static variable instance of the same class type Singleton, which holds the single instance of the class.
- We have a private constructor to prevent instantiation of the class from outside.
- We provide a public static method getInstance() that returns the single instance of the class. This method ensures lazy initialization, meaning the instance is created only when it’s first requested. Subsequent calls to getInstance() return the same instance.
- Other methods and properties of the class can be added as needed.
- In this example, singleton1 and singleton2 refer to the same instance of the Singleton class, confirming that the Singleton pattern has been implemented successfully.
Factory Method Pattern:
- The Factory Method pattern is a creational design pattern that provides an interface for creating objects in a superclass but allows subclasses to alter the type of objects that will be created.
- This pattern promotes loose coupling by eliminating the need for a client class to know which concrete class it needs to instantiate.
- Following is a basic implementation of the Factory Method pattern in Java:
public class Test {
public static void main(String[] args) {
Creator creatorA = new ConcreteCreatorA();
Product productA = creatorA.factoryMethod();
productA.operation();
Creator creatorB = new ConcreteCreatorB();
Product productB = creatorB.factoryMethod();
productB.operation();
}
}
//Abstract Product
interface Product {
void operation();
}
//Concrete Products
class ConcreteProductA implements Product {
@Override
public void operation() {
System.out.println(“Concrete Product A operation”);
}
}
class ConcreteProductB implements Product {
@Override
public void operation() {
System.out.println(“Concrete Product B operation”);
}
}
//Creator (Abstract Factory) for creating factory objects
abstract class Creator {
// Factory method to create products
public abstract Product factoryMethod();
}
//Concrete Creators
class ConcreteCreatorA extends Creator {
@Override
public Product factoryMethod() {
return new ConcreteProductA();
}
}
class ConcreteCreatorB extends Creator {
@Override
public Product factoryMethod() {
return new ConcreteProductB();
}
}
In this example:
- We have an interface Product and two concrete implementations ConcreteProductA and ConcreteProductB.
- We have an abstract class Creator, which declares the factory method factoryMethod(). This method is responsible for creating products.
- Subclasses override this method to instantiate different types of products.
- We have concrete subclasses ConcreteCreatorA and ConcreteCreatorB, which override the factoryMethod() to return instances of ConcreteProductA and ConcreteProductB respectively.
- The client class Test uses the factory method to create products without knowing the concrete classes of the products.
- This pattern allows for flexibility in object creation, as the client code depends only on the abstract Creator class and Product interface, rather than concrete classes. It also makes it easy to introduce new types of products by creating new concrete subclasses of Creator.
Abstract Factory Pattern
- The Abstract Factory Pattern is a creational design pattern that provides an interface for creating families of related or dependent objects without specifying their concrete classes.
- It allows a client to create objects without knowing the specific classes or implementations of those objects, promoting loose coupling and flexibility.
- Here’s a basic example of implementing the Abstract Factory Pattern in Java:
//Client code
public class Test {
public static void main(String[] args) {
AbstractFactory factory1 = new ConcreteFactory1();
ProductA productA1 = factory1.createProductA();
ProductB productB1 = factory1.createProductB();
AbstractFactory factory2 = new ConcreteFactory2();
ProductA productA2 = factory2.createProductA();
ProductB productB2 = factory2.createProductB();
// Use the products…
}
}
//Abstract Product A
interface ProductA {
void operationA();
}
//Concrete Product A1
class ConcreteProductA1 implements ProductA {
@Override
public void operationA() {
System.out.println(“Concrete Product A1 operation”);
}
}
//Concrete Product A2
class ConcreteProductA2 implements ProductA {
@Override
public void operationA() {
System.out.println(“Concrete Product A2 operation”);
}
}
//Abstract Product B
interface ProductB {
void operationB();
}
//Concrete Product B1
class ConcreteProductB1 implements ProductB {
@Override
public void operationB() {
System.out.println(“Concrete Product B1 operation”);
}
}
//Concrete Product B2
class ConcreteProductB2 implements ProductB {
@Override
public void operationB() {
System.out.println(“Concrete Product B2 operation”);
}
}
//Abstract Factory
interface AbstractFactory {
ProductA createProductA();
ProductB createProductB();
}
//Concrete Factory 1
class ConcreteFactory1 implements AbstractFactory {
@Override
public ProductA createProductA() {
return new ConcreteProductA1();
}
@Override
public ProductB createProductB() {
return new ConcreteProductB1();
}
}
//Concrete Factory 2
class ConcreteFactory2 implements AbstractFactory {
@Override
public ProductA createProductA() {
return new ConcreteProductA2();
}
@Override
public ProductB createProductB() {
return new ConcreteProductB2();
}
}
In this example:
- We have two families of related products: ProductA and ProductB.
- Each family has two concrete implementations: ConcreteProductA1, ConcreteProductA2 for ProductA, and ConcreteProductB1, ConcreteProductB2 for ProductB.
- We define an AbstractFactory interface that declares abstract methods to create ProductA and ProductB objects.
- We have two concrete factory implementations, ConcreteFactory1 and ConcreteFactory2, which provide specific implementations for creating products of ProductA and ProductB families.
- The client code (Test class) uses the factories to create products without needing to know the specific implementations of those products.
- This pattern allows for easy addition of new families of products and ensures that the products within a family are compatible with each other. It promotes flexibility and maintainability by abstracting the creation of objects.
Builder Pattern
- The Builder Pattern is a creational design pattern that separates the construction of a complex object from its representation, allowing the same construction process to create different representations.
- It is particularly useful when dealing with objects that have a large number of optional parameters or configuration settings.
- Here’s a basic example of implementing the Builder Pattern in Java:
public class Test {
public static void main(String[] args) {
// Using the builder to create a Product object
Product product = new Product.Builder()
.attribute1(“Value1”)
.attribute2(“Value2”)
.attribute3(123).build();
System.out.println(product);
}
}
//Product class
class Product {
private String attribute1;
private String attribute2;
private int attribute3;
// Private constructor to prevent instantiation from outside
private Product(Builder builder) {
this.attribute1 = builder.attribute1;
this.attribute2 = builder.attribute2;
this.attribute3 = builder.attribute3;
}
// Getters for attributes
// Inner Builder class Inside Product
static class Builder {
private String attribute1;
private String attribute2;
private int attribute3;
// Builder methods to set values for attributes
public Builder attribute1(String value) {
this.attribute1 = value;
return this;
}
public Builder attribute2(String value) {
this.attribute2 = value;
return this;
}
public Builder attribute3(int value) {
this.attribute3 = value;
return this;
}
// Build method to create the product instance
public Product build() {
return new Product(this);
}
}
}
In this example:
- We have a Product class with several attributes and a private constructor that takes a Builder object as its argument.
- We have a static nested Builder class within the Product class. This builder class has methods to set the values for each attribute and a build() method to create the Product object.
- The client code (Test class) uses the builder to create a Product object, setting the values for the attributes as needed.
- The Builder Pattern allows for a fluent and readable way to construct complex objects with many optional parameters. It also allows for the immutability of the constructed objects, as the attributes are set via the builder and cannot be changed after object creation.
Prototype Pattern:
- The Prototype Pattern is a creational design pattern that allows you to create new objects by copying existing objects, known as prototypes, rather than creating new instances from scratch.
- This pattern is useful when the construction of new objects is more expensive than copying an existing object.
- Here’s a basic example of implementing the Prototype Pattern in Java:
//Client code
public class Test {
public static void main(String[] args) {
// Create a prototype object
ConcretePrototype prototype = new ConcretePrototype(“Original Attribute”);
// Clone the prototype to create new objects
ConcretePrototype clone1 = (ConcretePrototype) prototype.clone();
ConcretePrototype clone2 = (ConcretePrototype) prototype.clone();
System.out.println(“Original Attribute: ” + prototype.getAttribute());
System.out.println(“clone1 Attribute before modifying: ” + clone1.getAttribute());
// Modify the attribute of the clones
clone1.setAttribute(“Modified Attribute 1”);
clone2.setAttribute(“Modified Attribute 2”);
// Output the attributes of the original and the clones
System.out.println(“———– Attributes after modification”);
System.out.println(“Original Attribute: ” + prototype.getAttribute());
System.out.println(“Clone 1 Attribute: ” + clone1.getAttribute());
System.out.println(“Clone 2 Attribute: ” + clone2.getAttribute());
}
}
//Prototype interface
interface Prototype {
Prototype clone();
}
//Concrete prototype class
class ConcretePrototype implements Prototype {
private String attribute;
public ConcretePrototype(String attribute) {
this.attribute = attribute;
}
// Copy constructor
public ConcretePrototype(ConcretePrototype prototype) {
this.attribute = prototype.attribute;
}
@Override
public Prototype clone() {
return new ConcretePrototype(this);
}
public void setAttribute(String attribute) {
this.attribute = attribute;
}
public String getAttribute() {
return attribute;
}
}
In this example:
- We have a Prototype interface with a single method clone() that returns a copy of the object.
- We have a ConcretePrototype class that implements the Prototype interface. It has a constructor to set the initial attribute value and a copy constructor to copy the attribute value from another object.
- In the client code (Test class), we create a prototype object and then clone it to create new objects. We modify the attributes of the clones independently.
- The Prototype Pattern allows for the efficient creation of new objects by copying existing objects. It reduces the overhead of creating new instances and allows for flexibility in creating new objects with different configurations.
Structural Patterns:
Adapter Pattern
- The Adapter Pattern is a structural design pattern that allows objects with incompatible interfaces to work together.
- It acts as a bridge between two incompatible interfaces by converting the interface of a class into another interface that a client expects.
- Here’s a basic example of implementing the Adapter Pattern in Java:
public class Test {
public static void main(String[] args) {
// Create an Adaptee object
Adaptee adaptee = new Adaptee();
// Create an Adapter object, passing the Adaptee object to its constructor
Target adapter = new Adapter(adaptee);
// Call the request method on the adapter
adapter.request();
}
}
// Target interface
interface Target {
void request();
}
// Adaptee (the class that needs to be adapted)
class Adaptee {
public void specificRequest() {
System.out.println(“Adaptee’s specific request”);
}
}
// Adapter class
class Adapter implements Target {
private Adaptee adaptee;
public Adapter(Adaptee adaptee) {
this.adaptee = adaptee;
}
@Override
public void request() {
// Call the specificRequest method of the adaptee
adaptee.specificRequest();
}
}
In this example:
- We have a Target interface that defines the expected interface that the client uses.
- We have an Adaptee class that has a method (specificRequest()) with a different interface that needs to be adapted.
- We have an Adapter class that implements the Target interface and contains a reference to the Adaptee object. It delegates calls to the request() method to the specificRequest() method of the Adaptee.
- In the client code (Test class), we create an Adaptee object and an Adapter object, passing the Adaptee object to the Adapter constructor. We then call the request() method on the Adapter, which internally calls the specificRequest() method of the Adaptee.
- The Adapter Pattern allows objects with incompatible interfaces to work together without modifying their source code. It promotes reusability and flexibility by allowing the integration of new functionality into existing systems.
Decorator Pattern
- The Decorator Pattern is a structural design pattern that allows behavior to be added to individual objects, either statically or dynamically, without affecting the behavior of other objects from the same class.
- It is useful for situations where you need to add new functionality to an object without subclassing.
- Here’s a basic example of implementing the Decorator Pattern in Java:
public class Test {
public static void main(String[] args) {
// Create a concrete component
Component component = new ConcreteComponent();
// Decorate the component with ConcreteDecoratorA
Component decoratedComponentA = new ConcreteDecoratorA(component);
decoratedComponentA.operation();
System.out.println();
// Decorate the component with ConcreteDecoratorB
Component decoratedComponentB = new ConcreteDecoratorB(component);
decoratedComponentB.operation();
}
}
//Component interface
interface Component {
void operation();
}
//Concrete component
class ConcreteComponent implements Component {
@Override
public void operation() {
System.out.println(“ConcreteComponent operation”);
}
}
//Decorator abstract class
abstract class Decorator implements Component {
protected Component component;
public Decorator(Component component) {
this.component = component;
}
@Override
public void operation() {
component.operation();
}
}
//Concrete decorator
class ConcreteDecoratorA extends Decorator {
public ConcreteDecoratorA(Component component) {
super(component);
}
@Override
public void operation() {
super.operation();
addedBehavior();
}
private void addedBehavior() {
System.out.println(“Added behavior by ConcreteDecoratorA”);
}
}
//Another concrete decorator
class ConcreteDecoratorB extends Decorator {
public ConcreteDecoratorB(Component component) {
super(component);
}
@Override
public void operation() {
super.operation();
addedBehavior();
}
private void addedBehavior() {
System.out.println(“Added behavior by ConcreteDecoratorB”);
}
}
In this example:
- We have a Component interface that defines the operation to be performed.
- We have a ConcreteComponent class that implements the Component interface and provides the base behavior.
- We have a Decorator abstract class that implements the Component interface and contains a reference to another Component object. It delegates the operation to the wrapped component and adds additional behavior.
- We have concrete decorator classes ConcreteDecoratorA and ConcreteDecoratorB, which extend the Decorator class and add specific behavior before or after delegating to the wrapped component.
- In the client code (Test class), we create a ConcreteComponent object and decorate it with ConcreteDecoratorA and ConcreteDecoratorB. We then call the operation() method on the decorated objects, which results in the base behavior being executed along with the added behavior from the decorators.
- The Decorator Pattern allows for the dynamic addition of new functionality to objects at runtime, promoting flexibility and maintainability by avoiding the need for subclassing to add functionality.
Proxy Pattern
- The Proxy Pattern is a structural design pattern that provides a surrogate or placeholder for another object to control access to it.
- It is commonly used to implement lazy loading, access control, logging, and monitoring. There are different types of proxies, such as virtual proxies, protection proxies, and remote proxies.
- Here’s a basic example of implementing the Proxy Pattern in Java:
public class Test {
public static void main(String[] args) {
// Use the Proxy to interact with the RealSubject
RealSubject proxy = new Proxy();
proxy.request();
}
}
//Subject interface
interface RealSubject {
void request();
}
//RealSubject class
class RealSubjectImpl implements RealSubject {
@Override
public void request() {
System.out.println(“RealSubject’s request”);
}
}
//Proxy class
class Proxy implements RealSubject {
private RealSubject realSubject;
public Proxy() {
// This could be lazy-loaded, initialized on demand
realSubject = new RealSubjectImpl();
}
@Override
public void request() {
// Perform additional operations before delegating to the RealSubject
System.out.println(“Proxy’s request – Before”);
// Delegate the request to the RealSubject
realSubject.request();
// Perform additional operations after delegating to the RealSubject
System.out.println(“Proxy’s request – After”);
}
}
In this example:
- We have a RealSubject interface that declares the request() method.
- We have a RealSubjectImpl class that implements the RealSubject interface, representing the real object.
- We have a Proxy class that also implements the RealSubject interface. It has a reference to the real subject and delegates the request() method to it, performing additional operations before and after the delegation.
- The Proxy class acts as a surrogate for the RealSubject and controls access to it. In this case, it performs additional operations before and after the real subject’s request() method is called.
- The Proxy Pattern is beneficial when you want to control access to an object, add additional functionality, or delay the creation and initialization of the real object until it is actually needed.
Composite Pattern
- The Composite Pattern is a structural design pattern that allows you to compose objects into tree-like structures to represent part-whole hierarchies.
- It lets clients treat individual objects and compositions of objects uniformly.
- Here’s a basic example of implementing the Composite Pattern in Java:
import java.util.List;
//Client code
public class Test {
public static void main(String[] args) {
// Create leaf components
Component leaf1 = new Leaf(“1”);
Component leaf2 = new Leaf(“2”);
Component leaf3 = new Leaf(“3”);
// Create composite component and add leaf components
Composite composite = new Composite();
composite.add(leaf1);
composite.add(leaf2);
// Create another composite component and add the first composite and a leaf
Composite composite2 = new Composite();
composite2.add(composite);
composite2.add(leaf3);
// Perform operation on the composite
composite2.operation();
}
}
// Component interface
interface Component {
void operation();
}
// Leaf class
class Leaf implements Component {
private String name;
public Leaf(String name) {
this.name = name;
}
@Override
public void operation() {
System.out.println(“Leaf ” + name + ” operation”);
}
}
// Composite class
class Composite implements Component {
private List
public void add(Component component) {
children.add(component);
}
public void remove(Component component) {
children.remove(component);
}
@Override
public void operation() {
for (Component component : children) {
component.operation();
}
}
}
In this example:
- We have a Component interface that declares the operation() method.
- We have a Leaf class that implements the Component interface. It represents individual objects in the composition.
- We have a Composite class that also implements the Component interface. It represents composite objects that can have children (leaf or composite components).
- In the Composite class, we maintain a list of child components. The operation() method iterates over the children and invokes their operation() methods recursively.
- In the client code (Test class), we create leaf and composite components and compose them into a tree-like structure. We then operate on the composite component, which recursively operates on all its children.
- The Composite Pattern allows you to represent complex hierarchical structures as a composition of simple objects, making it easy to work uniformly with individual objects and groups of objects.
Bridge Pattern
- The Bridge Pattern is a structural design pattern that separates an abstraction from its implementation, allowing both to vary independently.
- It is useful when you want to avoid a permanent binding between an abstraction and its implementation, or when you want to extend the hierarchy of abstractions and implementations independently.
- Here’s a basic example of implementing the Bridge Pattern in Java:
public class Test {
public static void main(String[] args) {
// Create concrete implementors
Implementor implementorA = new ConcreteImplementorA();
Implementor implementorB = new ConcreteImplementorB();
// Create refined abstractions with different implementors
Abstraction abstractionA = new RefinedAbstraction(implementorA);
Abstraction abstractionB = new RefinedAbstraction(implementorB);
// Perform operations on abstractions
abstractionA.operation();
abstractionB.operation();
}
}
//Implementor interface
interface Implementor {
void operationImpl();
}
//Concrete Implementor A
class ConcreteImplementorA implements Implementor {
@Override
public void operationImpl() {
System.out.println(“Concrete Implementor A operation”);
}
}
//Concrete Implementor B
class ConcreteImplementorB implements Implementor {
@Override
public void operationImpl() {
System.out.println(“Concrete Implementor B operation”);
}
}
//Abstraction class
abstract class Abstraction {
protected Implementor implementor;
public Abstraction(Implementor implementor) {
this.implementor = implementor;
}
public abstract void operation();
}
//Refined Abstraction class
class RefinedAbstraction extends Abstraction {
public RefinedAbstraction(Implementor implementor) {
super(implementor);
}
@Override
public void operation() {
System.out.println(“Refined Abstraction operation”);
implementor.operationImpl();
}
}
In this example:
- We have an Implementor interface that defines the operations for the implementation classes.
- We have two concrete implementor classes, ConcreteImplementorA and ConcreteImplementorB, which provide different implementations of the operations.
- We have an Abstraction abstract class that holds a reference to an Implementor object and provides methods for clients to interact with.
- We have a RefinedAbstraction class that extends Abstraction and provides additional functionality on top of the abstraction.
- In the client code (Test class), we create concrete implementors and refined abstractions, passing different implementors to each refined abstraction. We then perform operations on the abstractions, which are delegated to their respective implementors.
- The Bridge Pattern allows you to decouple an abstraction from its implementation, enabling you to vary them independently. This promotes flexibility and easier extension of the system without modifying existing code.
Behavioral Patterns
Observer Pattern:
- The Observer Pattern is a behavioral design pattern where an object(subject) maintains a list of its dependents(observers) and notifies them of any state changes, usually by calling one of their methods.
- This pattern is used to establish one-to-many relationships between objects, ensuring that when one object changes state, all its dependents are notified and updated automatically.
- Here’s how you can implement the Observer Pattern in Java:
import java.util.List;
//Client code
public class Test {
public static void main(String[] args) {
ConcreteSubject subject = new ConcreteSubject();
ConcreteObserver observer1 = new ConcreteObserver(subject);
ConcreteObserver observer2 = new ConcreteObserver(subject);
subject.setState(10); // This will notify both observers
}
}
// Subject interface
interface Subject {
void attach(Observer observer); // Method to attach an observer
void detach(Observer observer); // Method to detach an observer
void notifyObservers(); // Method to notify all observers
}
// Concrete Subject class
class ConcreteSubject implements Subject {
private int state; // State of the subject
private List
public int getState() {
return state;
}
public void setState(int state) {
this.state = state;
notifyObservers(); // Notify all observers when the state changes
}
@Override
public void attach(Observer observer) {
observers.add(observer);
}
@Override
public void detach(Observer observer) {
observers.remove(observer);
}
@Override
public void notifyObservers() {
for (Observer observer : observers) {
observer.update();
}
}
}
// Observer interface
interface Observer {
void update(); // Method called by the subject to notify observer of changes
}
// Concrete Observer class
class ConcreteObserver implements Observer {
private ConcreteSubject subject; // Reference to the subject
public ConcreteObserver(ConcreteSubject subject) {
this.subject = subject;
this.subject.attach(this); // Attach this observer to the subject
}
@Override
public void update() {
System.out.println(“Observer updated with new state: ” + subject.getState());
}
}
In this example:
- The Subject is an interface defining methods to attach, detach, and notify observers.
- ConcreteSubject is a class implementing the Subject interface. It maintains a list of observers and notifies them when its state changes.
- Observer is an interface defining a method to update observers when the subject changes.
- ConcreteObserver is a class implementing the Observer interface. It registers itself with the subject during instantiation and gets notified of state changes.
- When you run the Test class, both observers will be notified when the subject’s state changes. This demonstrates the Observer Pattern’s ability to establish a one-to-many relationship between the subject and its observers.
Strategy pattern
- The Strategy Pattern is a behavioral design pattern that allows you to define a family of algorithms, encapsulate each one as an object, and make them interchangeable.
- It lets the algorithm vary independently from the clients that use it.
- Here’s a basic example of implementing the Strategy Pattern in Java:
public class Test {
public static void main(String[] args) {
// Create strategies
Strategy strategyA = new ConcreteStrategyA();
Strategy strategyB = new ConcreteStrategyB();
// Create context with strategy A
Context context = new Context(strategyA);
// Execute strategy A
context.executeStrategy();
// Set context to use strategy B
context.setStrategy(strategyB);
// Execute strategy B
context.executeStrategy();
}
}
//Strategy interface
interface Strategy {
void execute();
}
//Concrete strategy A
class ConcreteStrategyA implements Strategy {
@Override
public void execute() {
System.out.println(“Executing strategy A”);
}
}
//Concrete strategy B
class ConcreteStrategyB implements Strategy {
@Override
public void execute() {
System.out.println(“Executing strategy B”);
}
}
//Context class
class Context {
private Strategy strategy;
public Context(Strategy strategy) {
this.strategy = strategy;
}
public void setStrategy(Strategy strategy) {
this.strategy = strategy;
}
public void executeStrategy() {
strategy.execute();
}
}
In this example:
- We have a Strategy interface that declares a method for executing a strategy.
- We have two concrete strategy classes, ConcreteStrategyA and ConcreteStrategyB, each implementing the Strategy interface with its own implementation of the execute() method.
- We have a Context class that holds a reference to a strategy object and provides methods for clients to set and execute strategies.
- In the client code (Test class), we create instances of concrete strategies and a context object. We set the context to use strategy A initially and then execute it. Next, we change the strategy to B and execute it.
- The Strategy Pattern allows for easy interchangeability of algorithms or strategies at runtime, making it flexible and promoting code reuse. It also helps in encapsulating the algorithm implementation details from the client code.
Command pattern
- The Command Pattern is a behavioral design pattern that encapsulates a request as an object, thereby allowing for parameterization of clients with queues, requests, and operations.
- It promotes loose coupling between the sender and receiver of a request.
- Here’s a basic example of implementing the Command Pattern in Java:
public class Test {
public static void main(String[] args) {
// Create receiver
Receiver receiver = new Receiver();
// Create command and associate with receiver
Command command = new ConcreteCommand(receiver);
// Create invoker and set the command
Invoker invoker = new Invoker();
invoker.setCommand(command);
// Execute the command
invoker.executeCommand();
}
}
//Command interface
interface Command {
void execute();
}
//Receiver
class Receiver {
public void action() {
System.out.println(“Receiver executing action”);
}
}
//Concrete command
class ConcreteCommand implements Command {
private Receiver receiver;
public ConcreteCommand(Receiver receiver) {
this.receiver = receiver;
}
@Override
public void execute() {
receiver.action();
}
}
//Invoker
class Invoker {
private Command command;
public void setCommand(Command command) {
this.command = command;
}
public void executeCommand() {
command.execute();
}
}
In this example:
- We have a Command interface that declares a method for executing a command.
- We have a Receiver class that defines the actual action to be performed when the command is executed.
- We have a ConcreteCommand class that implements the Command interface and holds a reference to a Receiver object. It invokes the action on the receiver when the command is executed.
- We have an Invoker class that holds a reference to a command object and provides a method to set and execute the command.
- In the client code (Test class), we create instances of the receiver, concrete command, and invoker. We associate the command with the receiver and set it in the invoker. We then execute the command using the invoker.
- The Command Pattern decouples the sender of a request from its receiver, allowing for parameterization of clients with requests. It also allows for the queuing of requests, logging, and undoable operations.
Template Method Pattern
- The Template Method Pattern is a behavioral design pattern that defines the skeleton of an algorithm in the superclass but lets subclasses override specific steps of the algorithm without changing its structure.
- It promotes reusability and helps in enforcing a common behavior across multiple classes.
- Here’s a basic example of implementing the Template Method Pattern in Java:
public class Test {
public static void main(String[] args) {
AbstractClass object1 = new ConcreteClass1();
AbstractClass object2 = new ConcreteClass2();
System.out.println(“Object 1:”);
object1.templateMethod();
System.out.println(“\nObject 2:”);
object2.templateMethod();
}
}
//Abstract class defining the template method
abstract class AbstractClass {
// Template method
public void templateMethod() {
// Common steps that every subclass will follow
step1();
step2();
step3();
}
// Abstract methods to be implemented by subclasses
protected abstract void step1();
protected abstract void step2();
// Concrete method with a default implementation
protected void step3() {
System.out.println(“Default implementation of step3”);
}
}
//Concrete subclass 1
class ConcreteClass1 extends AbstractClass {
@Override
protected void step1() {
System.out.println(“ConcreteClass1: Step 1”);
}
@Override
protected void step2() {
System.out.println(“ConcreteClass1: Step 2”);
}
}
//Concrete subclass 2
class ConcreteClass2 extends AbstractClass {
@Override
protected void step1() {
System.out.println(“ConcreteClass2: Step 1”);
}
@Override
protected void step2() {
System.out.println(“ConcreteClass2: Step 2”);
}
// Override step3 if needed
@Override
protected void step3() {
System.out.println(“Custom implementation of step3 for ConcreteClass2”);
}
}
In this example:
- We have an AbstractClass defining the template method templateMethod() which outlines the algorithm steps and delegates the implementation of some steps to subclasses.
- The AbstractClass also declares abstract methods step1() and step2() which are implemented by concrete subclasses.
- There’s a default implementation for step3() which subclasses can optionally override.
- ConcreteClass1 and ConcreteClass2 are subclasses of AbstractClass that provide their implementations of step1() and step2().
- In the client code (Test class), we demonstrate the usage of both concrete subclasses by invoking their templateMethod().
- The Template Method Pattern promotes code reuse and provides a way to define the skeleton of an algorithm in a superclass while allowing subclasses to provide their implementations for specific steps.
State pattern
- The State Pattern is a behavioral design pattern that allows an object to alter its behavior when its internal state changes.
- The pattern encapsulates states into separate classes and delegates the state-specific behavior to these classes. It promotes loose coupling and simplifies the maintenance of state-dependent behavior.
- Here’s a basic example of implementing the State Pattern in Java:
public class Test {
public static void main(String[] args) {
// Create context
Context context = new Context();
// Perform requests with different states
context.request(); // Output: Handling state A
// Change state
context.setState(new ConcreteStateB());
context.request(); // Output: Handling state B
}
}
//Context class
class Context {
private State state;
public Context() {
// Set initial state
state = new ConcreteStateA();
}
public void setState(State state) {
this.state = state;
}
// Delegate state-specific behavior to the current state object
public void request() {
state.handle();
}
}
//State interface
interface State {
void handle();
}
//Concrete state A
class ConcreteStateA implements State {
@Override
public void handle() {
System.out.println(“Handling state A”);
}
}
//Concrete state B
class ConcreteStateB implements State {
@Override
public void handle() {
System.out.println(“Handling state B”);
}
}
In this example:
- We have a Context class that maintains a reference to the current state object and delegates state-specific behavior to this object.
- We have a State interface that defines a method handle() which encapsulates the behavior associated with a particular state.
- We have concrete state classes ConcreteStateA and ConcreteStateB that implement the State interface and provide their implementations of the handle() method.
- In the client code (Test class), we create a Context object and perform requests with different states. We then change the state of the context and perform another request to observe the behavior change.
- The State Pattern allows an object to behave differently based on its internal state without changing its interface. It simplifies complex conditional logic and promotes the encapsulation of state-specific behavior into separate classes.