Design Patterns
===============
Design patterns are reusable solutions to common problems that arise during the software design process. They provide a proven approach to solving specific design issues, making it easier for developers to create maintainable, scalable, and efficient software systems.
1. Creational Patterns
Creational patterns focus on the creation of objects and their lifecycle. These patterns provide guidelines for designing classes that manage resources and create instances.
Singleton Pattern
The Singleton pattern ensures that only one instance of a class is created. It achieves this by restricting access to the constructor or by using a protected member variable.
// SingletonPattern.java
public class Singleton {
private static Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
public void doSomething() {
System.out.println("Singleton instance created");
}
}
// Usage
public class Main {
public static void main(String[] args) {
Singleton singleton1 = Singleton.getInstance();
Singleton singleton2 = Singleton.getInstance();
// Both singleton1 and singleton2 will point to the same instance
singleton1.doSomething(); // Output: Singleton instance created
}
}
Factory Pattern
The Factory pattern provides a way to create objects without specifying the exact class of object that will be created. It abstracts the creation process, allowing for polymorphism and flexibility.
// FactoryPattern.java
// Product interface
interface Pizza {
void makePizza();
}
// Concrete product classes
class PepperoniPizza implements Pizza {
public void makePizza() {
System.out.println("Making pepperoni pizza");
}
}
class VeggiePizza implements Pizza {
public void makePizza() {
System.out.println("Making veggie pizza");
}
}
// Factory interface
interface PizzaFactory {
Pizza createPizza();
}
// Concrete factory class
class PizzaFactoryImpl implements PizzaFactory {
@Override
public Pizza createPizza() {
return new PepperoniPizza();
}
}
public class Main {
public static void main(String[] args) {
PizzaFactory factory = new PizzaFactoryImpl();
// Create a pepperoni pizza using the factory
Pizza pepperoniPizza = factory.createPizza();
// Use the created pizza
pepperoniPizza.makePizza(); // Output: Making pepperoni pizza
}
}
Prototype Pattern
The Prototype pattern is used to create new objects by copying existing ones. It allows for efficient creation of duplicates and reduces the overhead of object creation.
// PrototypePattern.java
import java.util.ArrayList;
import java.util.List;
public class Prototype {
private static List<Prototype> instances = new ArrayList<>();
public static Prototype getInstance() {
if (instances.size() == 0) {
instances.add(new Prototype());
}
return instances.get(0);
}
public void doSomething() {
System.out.println("Prototype instance created");
}
public static void main(String[] args) {
Prototype prototype1 = Prototype.getInstance();
Prototype prototype2 = Prototype.getInstance();
// Both prototypes will point to the same instance
prototype1.doSomething(); // Output: Prototype instance created
}
}
2. Structural Patterns
Structural patterns focus on how classes are related and how they interact with each other.
Adapter Pattern
The Adapter pattern allows two incompatible objects to work together by converting the interface of one object into an interface that the other object can understand.
// AdapterPattern.java
// Target class
interface Shape {
void draw();
}
// Adaptee class
class Circle implements Shape {
public void draw() {
System.out.println("Drawing a circle");
}
}
public class Main {
public static void main(String[] args) {
// Get an instance of the adaptee (Circle)
Shape shape = new Circle();
// Use the adaptee with the target class
shape.draw();
}
}
Bridge Pattern
The Bridge pattern separates an interface from its implementation, allowing for flexibility and extensibility.
// BridgePattern.java
// Duck Interface
interface WaterFowl {
void quack();
}
// Mocker Class
class Turkey implements WaterFowl {
@Override
public void quack() {
System.out.println("Quacking like a turkey");
}
}
public class Main {
public static void main(String[] args) {
// Get an instance of the duck (Turkey)
WaterFowl waterFowl = new Turkey();
// Use the duck with the mocker
waterFowl.quack(); // Output: Quacking like a turkey
}
}
Composite Pattern
The Composite pattern allows multiple objects to be stored in an object, and each of them should have methods to manipulate it.
// CompositePattern.java
// Leaf class
class Apple {}
// Composite class
class Composite {
private List<Apple> apples = new ArrayList<>();
public void addApples(Apple apple) {
apples.add(apple);
}
@Override
public String toString() {
return "Composite{" +
"apples=" + apples +
'}';
}
}
public class Main {
public static void main(String[] args) {
// Create a composite tree
Composite composite = new Composite();
Apple apple1 = new Apple();
Apple apple2 = new Apple();
composite.addApples(apple1);
composite.addApples(apple2);
System.out.println(composite.toString()); // Output: Composite{apples=[Apple@6e3d55ee, Apple@6e3d55ef]}
}
}
3. Behavioral Patterns
Behavioral patterns focus on the interactions between objects and their environment.
Interpreter Pattern
The Interpreter pattern allows an object to be converted into a format that can be executed by another object.
// InterpreterPattern.java
// Command interface
interface Calculator {
int calculate(String operand);
}
// Concrete command class
class AddCommand implements Calculator {
@Override
public int calculate(String operand) {
return Integer.parseInt(operand) + 10;
}
}
public class Main {
public static void main(String[] args) {
// Create a calculator
Calculator calculator = new AddCommand();
// Use the calculator to evaluate an expression
System.out.println(calculator.calculate("5+3")); // Output: 8
}
}
Iterator Pattern
The Iterator pattern provides a way to access and traverse the elements of a collection without modifying it.
// IteratorPattern.java
import java.util.Iterator;
public class ArrayList implements Iterable<String> {
private String[] array = new String[5];
public void add(String item) {
array[item] = item;
}
@Override
public Iterator<String> iterator() {
return new ArrayListIterator();
}
}
class ArrayListIterator implements Iterator<String> {
@Override
public boolean hasNext() {
return !array.isEmpty();
}
@Override
public String next() {
return array[nextIndex];
}
private int nextIndex = 0;
public void remove() {
throw new UnsupportedOperationException("Not supported");
}
}
public class Main {
public static void main(String[] args) {
// Create an ArrayList
ArrayList arrayList = new ArrayList();
// Add elements to the list
for (int i = 1; i <= 5; i++) {
arrayList.add(i.toString());
}
// Use the iterator to access and traverse the elements
Iterator<String> iterator = arrayList.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next()); // Output: 1, 2, 3, 4, 5
}
}
}
4. Template Method Pattern
The Template Method pattern allows a class to provide an algorithm’s skeleton, including the beginning and end, while allowing subclasses to add specific steps without changing the algorithm’s structure.
// TemplateMethodPattern.java
public abstract class AbstractAlgorithm {
public void apply() {
System.out.println("Applying algorithm");
}
}
public class ConcreteAlgorithm extends AbstractAlgorithm {
@Override
protected void apply() {
System.out.println("Performing concrete steps");
}
public static void main(String[] args) {
// Create a template method with an implementation
AbstractAlgorithm algorithm = new ConcreteAlgorithm();
// Use the algorithm to execute its method
algorithm.apply();
}
}
5. Composite Design Pattern
The Composite design pattern is used to represent a tree or hierarchy of objects, and allows for efficient retrieval of hierarchical information.
// CompositeDesignPattern.java
// Element interface
interface Node {
String getName();
}
// Composite class
class Composite implements Node {
private List<Node> children = new ArrayList<>();
public void addChild(Node node) {
children.add(node);
}
@Override
public String getName() {
return "Composite";
}
}
public class Main {
public static void main(String[] args) {
// Create a composite tree
Composite root = new Composite();
Node child1 = new ConcreteNode("Child 1");
Node child2 = new ConcreteNode("Child 2");
root.addChild(child1);
root.addChild(child2);
System.out.println(root.getName()); // Output: Composite
}
}
class ConcreteNode implements Node {
private String name;
public ConcreteNode(String name) {
this.name = name;
}
@Override
public String getName() {
return name;
}
}