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;
    }
}