Singleton and Factory Patterns

Singleton Pattern

The Singleton pattern ensures that a class has only one instance and provides a global point of access to it. This is useful in cases where a single instance of a class is needed to coordinate actions across a system.


Key Points

- Only one instance of the class can exist.

- Provides a global point of access to the instance.


Implementation Example

public class Singleton {
    // Private static variable that holds the single instance
    private static Singleton instance;

    // Private constructor to prevent instantiation from outside the class
    private Singleton() {}

    // Public static method to provide access to the instance
    public static synchronized Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
    
    // Example method
    public void showMessage() {
        System.out.println("Hello World from Singleton!");
    }
}


Usage

public class Main {
    public static void main(String[] args) {
        // Get the only object available
        Singleton singleton = Singleton.getInstance();

        // Show the message
        singleton.showMessage();
    }
}


Factory Method Pattern

The Factory Method pattern defines an interface for creating an object but allows subclasses to alter the type of objects that will be created. This pattern is used when a class cannot anticipate the type of objects it needs to create beforehand.


Key Points

- Defines an interface for creating an object.

- Subclasses decide which class to instantiate.


Implementation Example

// Product interface
public interface Product {
    void use();
}

// Concrete Products
public class ConcreteProductA implements Product {
    public void use() {
        System.out.println("Using Product A");
    }
}

public class ConcreteProductB implements Product {
    public void use() {
        System.out.println("Using Product B");
    }
}

// Creator class
public abstract class Creator {
    // Factory method
    public abstract Product factoryMethod();

    public void someOperation() {
        Product product = factoryMethod();
        product.use();
    }
}

// Concrete Creators
public class ConcreteCreatorA extends Creator {
    public Product factoryMethod() {
        return new ConcreteProductA();
    }
}

public class ConcreteCreatorB extends Creator {
    public Product factoryMethod() {
        return new ConcreteProductB();
    }
}


Usage

public class Main {
    public static void main(String[] args) {
        // Create a creator for Product A
        Creator creatorA = new ConcreteCreatorA();
        creatorA.someOperation(); // Outputs: Using Product A

        // Create a creator for Product B
        Creator creatorB = new ConcreteCreatorB();
        creatorB.someOperation(); // Outputs: Using Product B
    }
}


Comparison

- Singleton Pattern:

  - Ensures a class has only one instance.

  - Provides a global point of access to the instance.

  - Useful for managing shared resources like a database connection or configuration settings.


- Factory Method Pattern:

  - Provides an interface for creating objects in a superclass but allows subclasses to alter the type of created objects.

  - Helps in decoupling the code that creates the object from the code that uses the object.

  - Useful for scenarios where a class cannot anticipate the type of objects it needs to create.


Both patterns are part of the creational design patterns family, but they solve different types of problems in object creation. The Singleton pattern focuses on managing a single instance of a class, while the Factory Method pattern focuses on creating objects without specifying the exact class of the object that will be created.

Design Patterns (Creational, Structural, Behavioral)

Design patterns are categorized into three main types: Creational, Structural, and Behavioral. Each category addresses different aspects of software design and provides a set of proven solutions to common problems.


Creational Design Patterns

Creational patterns focus on the process of object creation, ensuring that objects are created in a manner suitable to the situation.


1. Singleton Pattern:

   - Ensures a class has only one instance and provides a global point of access to it.

   - Example:

     public class Singleton {
         private static Singleton instance;

         private Singleton() {}

         public static synchronized Singleton getInstance() {
             if (instance == null) {
                 instance = new Singleton();
             }
             return instance;
         }
     }


2. Factory Method Pattern:

   - Defines an interface for creating an object but allows subclasses to alter the type of objects that will be created.

   - Example:

     public interface Product {
         void use();
     }

     public class ConcreteProductA implements Product {
         public void use() {
             System.out.println("Using Product A");
         }
     }

     public class ConcreteProductB implements Product {
         public void use() {
             System.out.println("Using Product B");
         }
     }

     public abstract class Creator {
         public abstract Product factoryMethod();

         public void someOperation() {
             Product product = factoryMethod();
             product.use();
         }
     }

     public class ConcreteCreatorA extends Creator {
         public Product factoryMethod() {
             return new ConcreteProductA();
         }
     }

     public class ConcreteCreatorB extends Creator {
         public Product factoryMethod() {
             return new ConcreteProductB();
         }
     }


3. Abstract Factory Pattern:

   - Provides an interface for creating families of related or dependent objects without specifying their concrete classes.

   - Example:

     public interface GUIFactory {
         Button createButton();
         Checkbox createCheckbox();
     }

     public class WinFactory implements GUIFactory {
         public Button createButton() {
             return new WinButton();
         }

         public Checkbox createCheckbox() {
             return new WinCheckbox();
         }
     }

     public class MacFactory implements GUIFactory {
         public Button createButton() {
             return new MacButton();
         }

         public Checkbox createCheckbox() {
             return new MacCheckbox();
         }
     }

     public class Application {
         private Button button;
         private Checkbox checkbox;

         public Application(GUIFactory factory) {
             button = factory.createButton();
             checkbox = factory.createCheckbox();
         }

         public void paint() {
             button.paint();
             checkbox.paint();
         }
     }


4. Builder Pattern:

   - Separates the construction of a complex object from its representation so that the same construction process can create different representations.

   - Example:

     public class Product {
         private String partA;
         private String partB;

         public void setPartA(String partA) {
             this.partA = partA;
         }

         public void setPartB(String partB) {
             this.partB = partB;
         }
     }

     public abstract class Builder {
         protected Product product = new Product();

         public abstract void buildPartA();
         public abstract void buildPartB();

         public Product getResult() {
             return product;
         }
     }

     public class ConcreteBuilder extends Builder {
         public void buildPartA() {
             product.setPartA("Part A");
         }

         public void buildPartB() {
             product.setPartB("Part B");
         }
     }

     public class Director {
         private Builder builder;

         public void setBuilder(Builder builder) {
             this.builder = builder;
         }

         public Product construct() {
             builder.buildPartA();
             builder.buildPartB();
             return builder.getResult();
         }
     }


5. Prototype Pattern:

   - Creates new objects by copying existing objects (prototypes).

   - Example:

     public abstract class Prototype implements Cloneable {
         public Prototype clone() throws CloneNotSupportedException {
             return (Prototype) super.clone();
         }
     }

     public class ConcretePrototype extends Prototype {
         private String field;

         public ConcretePrototype(String field) {
             this.field = field;
         }

         public String getField() {
             return field;
         }

         public void setField(String field) {
             this.field = field;
         }
     }

     public class Client {
         public static void main(String[] args) throws CloneNotSupportedException {
             ConcretePrototype prototype = new ConcretePrototype("Prototype");
             ConcretePrototype clone = (ConcretePrototype) prototype.clone();
             System.out.println(clone.getField());
         }
     }


Structural Design Patterns

Structural patterns deal with object composition, creating relationships between objects to form larger structures.


1. Adapter Pattern:

   - Allows incompatible interfaces to work together.

   - Example:

     public interface Target {
         void request();
     }

     public class Adaptee {
         public void specificRequest() {
             System.out.println("Specific request");
         }
     }

     public class Adapter implements Target {
         private Adaptee adaptee;

         public Adapter(Adaptee adaptee) {
             this.adaptee = adaptee;
         }

         public void request() {
             adaptee.specificRequest();
         }
     }

     public class Client {
         public static void main(String[] args) {
             Adaptee adaptee = new Adaptee();
             Target target = new Adapter(adaptee);
             target.request();
         }
     }


2. Composite Pattern:

   - Composes objects into tree structures to represent part-whole hierarchies.

   - Example:

     public interface Component {
         void operation();
     }

     public class Leaf implements Component {
         public void operation() {
             System.out.println("Leaf operation");
         }
     }

     public 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();
             }
         }
     }

     public class Client {
         public static void main(String[] args) {
             Leaf leaf1 = new Leaf();
             Leaf leaf2 = new Leaf();
             Composite composite = new Composite();
             composite.add(leaf1);
             composite.add(leaf2);
             composite.operation();
         }
     }


3. Decorator Pattern:

   - Adds behavior to objects dynamically by placing them inside special wrapper objects.

   - Example:

     public interface Component {
         void operation();
     }

     public class ConcreteComponent implements Component {
         public void operation() {
             System.out.println("ConcreteComponent operation");
         }
     }

     public class Decorator implements Component {
         protected Component component;

         public Decorator(Component component) {
             this.component = component;
         }

         public void operation() {
             component.operation();
         }
     }

     public class ConcreteDecorator extends Decorator {
         public ConcreteDecorator(Component component) {
             super(component);
         }

         public void operation() {
             super.operation();
             addedBehavior();
         }

         private void addedBehavior() {
             System.out.println("Added behavior");
         }
     }

     public class Client {
         public static void main(String[] args) {
             Component component = new ConcreteComponent();
             Component decoratedComponent = new ConcreteDecorator(component);
             decoratedComponent.operation();
         }
     }


4. Facade Pattern:

   - Provides a simplified interface to a complex subsystem.

   - Example:

     public class Subsystem1 {
         public void operation1() {
             System.out.println("Subsystem1 operation1");
         }
     }

     public class Subsystem2 {
         public void operation2() {
             System.out.println("Subsystem2 operation2");
         }
     }

     public class Facade {
         private Subsystem1 subsystem1;
         private Subsystem2 subsystem2;

         public Facade() {
             subsystem1 = new Subsystem1();
             subsystem2 = new Subsystem2();
         }

         public void operation() {
             subsystem1.operation1();
             subsystem2.operation2();
         }
     }

     public class Client {
         public static void main(String[] args) {
             Facade facade = new Facade();
             facade.operation();
         }
     }


5. Proxy Pattern:

   - Provides a surrogate or placeholder for another object to control access to it.

   - Example:

     public interface Subject {
         void request();
     }

     public class RealSubject implements Subject {
         public void request() {
             System.out.println("RealSubject request");
         }
     }

     public class Proxy implements Subject {
         private RealSubject realSubject;

         public void request() {
             if (realSubject == null) {
                 realSubject = new RealSubject();
             }
             realSubject.request();
         }
     }

     public class Client {
         public static void main(String[] args) {
             Subject proxy = new Proxy();
             proxy.request();
         }
     }


Behavioral Design Patterns

Behavioral patterns focus on communication between objects, making algorithms and the assignment of responsibilities among objects more flexible.


1. Strategy Pattern:

   - Defines a family of algorithms, encapsulates each one, and makes them interchangeable.

   - Example:

     public interface Strategy {
         int doOperation(int num1, int num2);
     }

     public class Addition implements Strategy {
         public int doOperation(int num1, int num2) {
             return num1 + num2;
         }
     }

     public class Subtraction implements Strategy {
         public int doOperation(int num1, int
 num2) {
             return num1 - num2;
         }
     }

     public class Multiplication implements Strategy {
         public int doOperation(int num1, int num2) {
             return num1 * num2;
         }
     }

     public class Context {
         private Strategy strategy;

         public Context(Strategy strategy) {
             this.strategy = strategy;
         }

         public int executeStrategy(int num1, int num2) {
             return strategy.doOperation(num1, num2);
         }
     }

     public class Client {
         public static void main(String[] args) {
             Context context = new Context(new Addition());
             System.out.println("10 + 5 = " + context.executeStrategy(10, 5));

             context = new Context(new Subtraction());
             System.out.println("10 - 5 = " + context.executeStrategy(10, 5));

             context = new Context(new Multiplication());
             System.out.println("10 * 5 = " + context.executeStrategy(10, 5));
         }
     }


2. Observer Pattern:

   - Defines a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.

   - Example:

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

         public void update(String message) {
             System.out.println(name + " received: " + message);
         }
     }

     public 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(String message) {
             for (Observer observer : observers) {
                 observer.update(message);
             }
         }
     }

     public class Client {
         public static void main(String[] args) {
             Subject subject = new Subject();

             Observer observer1 = new ConcreteObserver("Observer 1");
             Observer observer2 = new ConcreteObserver("Observer 2");

             subject.attach(observer1);
             subject.attach(observer2);

             subject.notifyObservers("State changed");
         }
     }


3. Command Pattern:

   - Encapsulates a request as an object, thereby allowing users to parameterize clients with queues, requests, and operations.

   - Example:

     public interface Command {
         void execute();
     }

     public class Light {
         public void on() {
             System.out.println("Light is ON");
         }

         public void off() {
             System.out.println("Light is OFF");
         }
     }

     public class LightOnCommand implements Command {
         private Light light;

         public LightOnCommand(Light light) {
             this.light = light;
         }

         public void execute() {
             light.on();
         }
     }

     public class LightOffCommand implements Command {
         private Light light;

         public LightOffCommand(Light light) {
             this.light = light;
         }

         public void execute() {
             light.off();
         }
     }

     public class RemoteControl {
         private Command command;

         public void setCommand(Command command) {
             this.command = command;
         }

         public void pressButton() {
             command.execute();
         }
     }

     public class Client {
         public static void main(String[] args) {
             Light light = new Light();
             Command lightOn = new LightOnCommand(light);
             Command lightOff = new LightOffCommand(light);

             RemoteControl remote = new RemoteControl();

             remote.setCommand(lightOn);
             remote.pressButton();

             remote.setCommand(lightOff);
             remote.pressButton();
         }
     }


4. State Pattern:

   - Allows an object to alter its behavior when its internal state changes. The object will appear to change its class.

   - Example:

     public interface State {
         void handle(Context context);
     }

     public class ConcreteStateA implements State {
         public void handle(Context context) {
             System.out.println("State A handling request.");
             context.setState(new ConcreteStateB());
         }
     }

     public class ConcreteStateB implements State {
         public void handle(Context context) {
             System.out.println("State B handling request.");
             context.setState(new ConcreteStateA());
         }
     }

     public class Context {
         private State state;

         public Context(State state) {
             this.state = state;
         }

         public void setState(State state) {
             this.state = state;
         }

         public void request() {
             state.handle(this);
         }
     }

     public class Client {
         public static void main(String[] args) {
             Context context = new Context(new ConcreteStateA());
             context.request();
             context.request();
         }
     }


5. Template Method Pattern:

   - Defines the skeleton of an algorithm in a method, deferring some steps to subclasses. Template Method lets subclasses redefine certain steps of an algorithm without changing the algorithm's structure.

   - Example:

     public abstract class AbstractClass {
         public final void templateMethod() {
             primitiveOperation1();
             primitiveOperation2();
             concreteOperation();
         }
         protected abstract void primitiveOperation1();
         protected abstract void primitiveOperation2();

         private void concreteOperation() {
             System.out.println("Concrete operation");
         }
     }

     public class ConcreteClass extends AbstractClass {
         protected void primitiveOperation1() {
             System.out.println("Primitive operation 1");
         }

         protected void primitiveOperation2() {
             System.out.println("Primitive operation 2");
         }
     }

     public class Client {
         public static void main(String[] args) {
             AbstractClass abstractClass = new ConcreteClass();
             abstractClass.templateMethod();
         }
     }


These examples cover a range of design patterns across the three main categories. Each pattern provides a structured approach to solving common software design problems, promoting code reuse, and improving maintainability.

Aspect-Oriented Programming (AOP)

Aspect-Oriented Programming (AOP) is a programming paradigm that aims to increase modularity by allowing the separation of cross-cutting concerns. Cross-cutting concerns are aspects of a program that affect multiple parts of the system, such as logging, security, transaction management, and error handling. AOP provides a way to modularize these concerns separately from the main business logic, promoting cleaner and more maintainable code.


Key Concepts of AOP:

1. Aspect:

   - An aspect is a module that encapsulates a cross-cutting concern. It contains advice, pointcuts, and introductions (inter-type declarations).


2. Advice:

   - Advice is the action taken by an aspect at a particular join point. Types of advice include:

     - Before Advice: Executes before the join point.

     - After Advice: Executes after the join point (regardless of its outcome).

     - After Returning Advice: Executes after the join point completes normally.

     - After Throwing Advice: Executes if the join point throws an exception.

     - Around Advice: Surrounds the join point, allowing pre- and post-processing.


3. Pointcut:

   - A pointcut defines a set of join points where advice should be applied. It acts as a predicate that matches join points and can be based on method signatures, annotations, or execution locations.


4. Join Point:

   - A join point is a specific point in the execution of the program, such as method execution, object instantiation, or field access, where an aspect can be applied.


5. Weaving:

   - Weaving is the process of linking aspects with other application types or objects to create an advised object. Weaving can occur at compile-time, load-time, or runtime.


Benefits of AOP:

1. Separation of Concerns:

   - By modularizing cross-cutting concerns into separate aspects, AOP promotes better separation of concerns, leading to more maintainable and understandable code.


2. Reusability:

   - Aspects can be reused across different parts of the application, reducing code duplication and improving consistency.


3. Maintainability:

   - Changes to cross-cutting concerns can be made in a single place (the aspect), simplifying maintenance and reducing the risk of introducing bugs.


4. Decoupling:

   - AOP decouples the business logic from cross-cutting concerns, allowing developers to focus on core functionality without being distracted by non-functional requirements.


Example with Spring AOP:

Spring AOP is a widely used AOP framework that integrates with the Spring framework. Here’s an example demonstrating the use of Spring AOP to log method execution times:


1. Aspect Definition:

   import org.aspectj.lang.ProceedingJoinPoint;
   import org.aspectj.lang.annotation.Around;
   import org.aspectj.lang.annotation.Aspect;
   import org.springframework.stereotype.Component;

   @Aspect
   @Component
   public class LoggingAspect {

       @Around("execution(* com.example.service.*.*(..))")
       public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
           long start = System.currentTimeMillis();

           Object proceed = joinPoint.proceed();

           long executionTime = System.currentTimeMillis() - start;

           System.out.println(joinPoint.getSignature() + " executed in " + executionTime + "ms");
           return proceed;
       }
   }


2. Service Class:

   package com.example.service;

   import org.springframework.stereotype.Service;

   @Service
   public class MyService {
       public void performTask() {
           // Business logic here
           System.out.println("Performing task");
       }
   }


3. Spring Configuration (if using XML-based configuration):

   <aop:aspectj-autoproxy />
   <bean id="loggingAspect" class="com.example.aspect.LoggingAspect" />
   <bean id="myService" class="com.example.service.MyService" />


   Or, if using Java-based configuration:

   @Configuration
   @EnableAspectJAutoProxy
   public class AppConfig {
       @Bean
       public LoggingAspect loggingAspect() {
           return new LoggingAspect();
       }

       @Bean
       public MyService myService() {
           return new MyService();
       }
   }


4. Application Context:

   import org.springframework.context.ApplicationContext;
   import org.springframework.context.annotation.AnnotationConfigApplicationContext;

   public class Main {
       public static void main(String[] args) {
           ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
           MyService myService = context.getBean(MyService.class);
           myService.performTask();
       }
   }


Conclusion

Aspect-Oriented Programming (AOP) is a powerful paradigm for modularizing cross-cutting concerns, making code more maintainable, reusable, and decoupled. Frameworks like Spring AOP provide tools to easily integrate AOP into Java applications, allowing developers to apply aspects declaratively and transparently.

HashMap in Java

Let’s break down how   HashMap   works internally (Java 8+ implementation). What Is a HashMap? HashMap<K, V>  is a  hash table–based  ...