The Programmer's Guide
  • About
  • Algorithm
    • Big O Notation
      • Tree
      • Problems
    • Basic Notes
    • Data Structure Implementation
      • Custom LinkedList
      • Custom Stack
      • Custom Queue
      • Custom Tree
        • Binary Tree Implementation
        • Binary Search Tree Implementation
        • Min Heap Implementation
        • Max Heap Implementation
        • Trie Implementation
      • Custom Graph
        • Adjacency List
        • Adjacency Matrix
        • Edge List
        • Bidirectional Search
    • Mathematical Algorithms
      • Problems - Set 1
      • Problems - Set 2
    • Bit Manipulation
      • Representation
      • Truth Tables
      • Number System
        • Java Program
      • Problems - Set 1
    • Searching
    • Sorting
    • Array Algorithms
    • String Algorithms
    • Tree
      • Tree Traversal Techniques
      • Tree Implementation
      • Applications of Trees
      • Problems - Set 1
    • Graph
      • Graph Traversal Techniques
      • Shortest Path Algorithms
      • Minimum Spanning Tree (MST) Algorithms
    • Dynamic Programming
      • Problems - Set 1
    • Recursion
    • Parallel Programming
    • Miscellaneous
      • Problems - Set 1
  • API
    • API Basics
      • What is an API?
      • Types of API
        • Comparison - TBU
      • Synchronous vs Asynchronous API
    • API Architecture
      • Synchronous & Asynchronous Communication
    • API Specification
  • Cloud Computing
    • Cloud Fundamentals
      • Cloud Terminology
      • Core Terminology
      • Cloud Models
      • Cloud Service Models
      • Benefits, Challenges and Risk of Cloud Computing
      • Cloud Ecosystem
  • Database
    • DBMS
      • Types of DBMS
        • Relational DBMS (RDBMS)
        • NoSQL DBMS
        • Object-Oriented DBMS (OODBMS)
        • Columnar DBMS
        • In-Memory DBMS
        • Distributed DBMS
        • Cloud-Based DBMS
        • Hierarchical DBMS
      • DBMS Architecture
      • DBMS Structure
    • SQL Databases
      • Terminology
      • RDBMS Concepts
        • Entity Relationship Diagram (ERD)
          • ERD Examples
        • Normalization
        • Denormalization
        • ACID & BASE Properties
          • ACID Properties
          • BASE Properties
        • Locking and Unlocking
      • SQL Fundamentals
        • SQL Commands
          • DDL (Data Definition Language)
          • DML (Data Manipulation Language)
          • DCL (Data Control Language)
          • TCL (Transaction Control Language)
          • DQL (Data Query Language)
        • SQL Operators
          • INTERSECT
          • EXCEPT
          • MINUS
          • IN and NOT IN
          • EXISTS and NOT EXISTS
        • SQL Clauses
          • Joins
          • OVER
          • WITH
          • CONNECT BY
          • MODEL
          • FETCH FIRST
          • KEEP
          • OFFSET with FETCH
        • SQL Functions
          • Oracle Specific
        • SQL Data Types
          • Numeric Types
          • Character Types
          • Date & Time Types
          • Large Object Types
        • Others
          • Indexing
      • Vendor Specific Concepts
        • Oracle Specific
          • Data Types
          • Character Set
          • Rownum, Rowid, Urowid
          • Order of Execution of the query
          • Keys
          • Tablespace
          • Partition
      • Best Practice
      • Resources & References
        • O’Reilly SQL Cookbook (2nd Edition)
          • 1. Retrieving Records
          • 2. Sorting Query Results
          • 3. Working with Multiple Tables
          • 4. Inserting, Updating, and Deleting
          • 5. Metadata Queries
          • 6. Working with Strings
          • 7. Working with Numbers
          • 8. Date Arithmetic
          • 9. Date Manipulation
          • 10. Working with Ranges
          • 11. Advanced Searching
          • 12. Reporting and Reshaping
          • 13. Hierarchical Queries
          • 14. Odds 'n' Ends
    • SQL vs NoSQL
    • Best Practices
  • Git
    • Commands
      • Setup and Configuration Commands
      • Getting and Creating Projects
      • Tracking Changes
      • Branching and Merging
      • Sharing and Updating Projects
      • Inspection and Comparison
      • Debugging
      • Patching
      • Stashing and Cleaning
      • Advanced Manipulations
    • Workflows
      • Branching Strategies
        • Git Flow
        • Trunk-Based Development
        • GitHub Flow
        • Comparison
      • Merge Strategies
        • Merge
        • Rebase
        • Squash
        • Fast-forward vs No-fast-forward
        • MR vs PR
      • Conflict Resolution
        • Handling Merge Conflicts
        • Merge Conflicts
        • Rebase Conflicts
        • Divergent Branches After git pull
        • Force Push
      • Patch & Recovery
        • Cherry-pick strategies
        • Revert vs Reset
        • Recover from a bad rebase
      • Rebasing Practices
        • Merge vs Rebase
        • Rebase develop branch on main branch
      • Repository Management
        • Working Directory
        • Mirror a repository
        • Convert a local folder to a Git repo
        • Backup and restore a Git repository
  • Java
    • Java Installation
    • Java Distributions
    • Java Platform Editions
      • Java SE
      • Java EE
      • Jakarta EE
      • Java ME
      • JavaFX
    • Java Overview
      • OOP Principles
        • Encapsulation
        • Inheritance
        • Polymorphism
        • Abstraction
          • Abstract Class & Method
          • Interface
            • Functional Interfaces
            • Marker Interfaces
          • Abstract Class vs Interface
      • OOP Basics
        • What is a Class?
          • Types of Classes
        • What is an Object?
          • Equals and HashCode
            • FAQ
          • Shallow Copy and Deep Copy
          • Ways to Create Object
          • Serialization & Deserialization
        • Methods & Fields
          • Method Overriding & Overloading
          • Method Signature & Header
          • Variables
        • Constructors
        • Access Modifiers
      • Parallelism & Concurrency
        • Ways to Identify Thread Concurrency or Parallelism
        • Thread Basics
          • Thread vs Process
          • Creating Threads
          • Thread Context Switching
          • Thread Lifecycle & States
          • Runnable & Callable
          • Types of Threads
          • Thread Priority
        • Thread Management & Synchronisation
          • Thread Resource Sharing
          • Thread Synchronization
            • Why is Synchronization Needed?
            • Synchronized Blocks & Methods
          • Thread Lock
            • Types of Locks
            • Intrinsic Lock (Monitor Lock)
            • Reentrant Lock
          • Semaphore
          • Thread Starvation
          • Thread Contention
          • Thread Deadlock
          • Best Practices for Avoiding Thread Issues
      • Keywords
        • this
        • super
        • Access Modifiers
      • Data Types
        • Default Values
        • Primitive Types
          • byte
          • short
          • int
          • long
          • float
          • double
          • char
          • boolean
        • Non-Primitive (Reference) Types
          • String
            • StringBuilder
            • StringBuffer
              • Problems
            • Multiline String
            • Comparison - String, StringBuilder & StringBuffer
          • Array
          • Collections
            • List
              • Array vs List
              • ArrayList
              • Vector
                • Stack
                  • Problems
              • LinkedList
            • Queue
              • PriorityQueue
              • Deque (Double-Ended Queue)
                • ArrayDeque
                • ConcurrentLinkedDeque - TBU
                • LinkedBlockingDeque - TBU
            • Map
              • HashMap
              • Hashtable
              • LinkedHashMap
              • ConcurrentHashMap
              • TreeMap
              • EnumMap
              • WeakHashMap
            • Set
              • HashSet
              • LinkedHashSet
              • TreeSet
              • EnumSet
              • ConcurrentSkipListSet
              • CopyOnWriteArraySet
        • Specialized Classes
          • BigInteger
          • BigDecimal
            • Examples
          • BitSet
          • Date and Time
            • Examples
          • Optional
          • Math
          • UUID
          • Scanner
          • Formatter
            • Examples
          • Properties
          • Regex (Pattern and Matcher)
            • Examples
          • Atomic Classes
          • Random
          • Format
            • NumberFormat
            • DateFormat
            • DecimalFormat
        • Others
          • Object
          • Enum
            • Pre-Defined Enum
            • Custom Enum
            • EnumSet and EnumMap
          • Record
          • Optional
          • System
          • Runtime
          • ProcessBuilder
          • Class
          • Void
          • Throwable
            • Error
            • Exception
              • Custom Exception Handling
              • Best Practice
            • Error vs Exception
            • StackTraceElement
    • Java Features by Version
      • How New Java Features are Released ?
      • Java Versions
        • Java 8
        • Java 9
        • Scoped Values
        • Unnamed Variables & Patterns
      • FAQ
    • Concepts
      • Set 1
        • Streams
          • flatmap
          • Collectors Utility Class
          • Problems
        • Functional Interfaces
          • Standard Built-In Interfaces
          • Custom Interfaces
        • Annotation
          • Custom Annotation
          • Meta Annotation
        • Generics
          • Covariance and Invariance
        • Asynchronous Computation
          • Future
          • CompletableFuture
          • Future v/s CompletableFuture
          • ExecutorService
            • Thread Pool
            • Types of Work Queues
            • Rejection Policies
            • ExecutorService Implementations
            • ExecutorService Usage
          • Locks, Atomic Variables, CountDownLatch, CyclicBarrier - TBU
          • Parallel Streams, Fork/Join Framework,Stream API with Parallelism - TBU
      • Set 2
        • Standards
          • ISO Standards
          • JSR
            • JSR 303, 349, 380 (Bean Validation)
        • Operator Precedence
      • Set 3
        • Date Time Formatter
        • Validation
      • Set 4
        • Input from User
        • Comparison & Ordering
          • Object Equality Check
          • Comparable and Comparator
            • Comparator Interface
          • Sorting of Objects
          • Insertion Ordering
    • Packages
      • Core Packages
        • java.lang
          • java.lang.System
          • java.lang.Thread
      • Jakarta Packages
        • jakarta.validation
        • javax.validation
      • Third-party Packages
    • Code Troubleshoot
      • Thread Dump
      • Heap Dump
    • Code Quality & Analysis
      • ArchUnit
      • Terminologies
        • Cyclic dependencies
    • Code Style
      • Naming Convention
      • Package Structure
      • Formatting
      • Comments and Documentation
      • Imports
      • Exception Handling
      • Class Structure
      • Method Guidelines
      • Page 1
      • Code Smells to Avoid
      • Lambdas and Streams Style
      • Tools
    • Tools
      • IntelliJ IDEA
        • Shortcuts for MAC
      • Apache JMeter
        • Examples
      • Thread Dump Capture
        • jstack
        • VisualVM - TBU
        • jcmd - TBU
        • JConsole - TBU
        • YourKit Java Profiler - TBU
        • Eclipse MAT - TBU
        • IntelliJ IDEA Profiler - TBU
        • AppDynamics - TBU
        • Dynatrace - TBU
        • Thread Dump Analyzers - TBU
      • Heap Dump Capture
        • jmap
        • VisualVM - TBU
        • jcmd - TBU
        • Eclipse MAT (Memory Analyzer Tool) - TBU
        • IntelliJ IDEA Profiler - TBU
        • YourKit Java Profiler - TBU
        • AppDynamics - TBU
        • Dynatrace - TBU
        • Kill -3 Command - TBU
        • jhat (Java Heap Analysis Tool) - TBU
        • JVM Options - TBU
      • Wireshark
        • Search Filters
    • Best Practices
      • Artifact and BOM Versioning
  • Maven
    • Installation
    • Local Repository & Configuration
    • Command-line Options
    • Build & Lifecycle
    • Dependency Management
      • Dependency
        • Transitive Dependency
        • Optional Dependency
      • Dependency Scope
        • Maven Lifecycle and Dependency Scope
      • Dependency Exclusions & Overrides
      • Bill of Materials (BOM)
      • Dependency Conflict Resolution
      • Dependency Tree & Analysis
      • Dependency Versioning Strategies
    • Plugins
      • Build Lifecycle Management
      • Dependency Management
      • Code Quality and Analysis
      • Documentation Generation
      • Code Generation
      • Packaging and Deployment
      • Reporting
      • Integration and Testing
      • Customization and Enhancement
        • build-helper-maven-plugin
        • properties-maven-plugin
        • ant-run plugin
        • exec-maven-plugin
        • gmavenplus-plugin
      • Performance Optimization
    • FAQs
      • Fixing Maven SSL Issues: Unable to Find Valid Certification Path
  • Spring
    • Spring Basics
      • What is Spring?
      • Why Use Spring
      • Spring Ecosystem
      • Versioning
      • Setting Up a Spring Project
    • Core Concepts
      • Spring Core
        • Dependency Injection (DI)
        • Stereotype Annotation
      • Spring Beans
        • Bean Lifecycle
        • Bean Scope
          • Singleton Bean
        • Lazy & Eager Initialization
          • Use Case of Lazy Initialization
        • BeanFactory
        • ApplicationContext
      • Spring Annotations
        • Spring Boot Specific
        • Controller Layer (Web & REST Controllers)
    • Spring Features
      • Auto Configuration
        • Spring Boot 2: spring.factories
        • Spring Boot 3: spring.factories
      • Spring Caching
        • In-Memory Caching
      • Spring AOP
        • Before Advice
        • After Returning Advice
        • After Throwing Advice
        • After (finally) Advice
        • Around Advice
      • Spring File Handling
      • Reactive Programming
        • Reactive System
        • Reactive Stream Specification
        • Project Reactor
          • Mono & Flux
      • Asynchronous Computation
        • @Async annotation
      • Spring Security
        • Authentication
          • Core Components
            • Security Filter Chain
              • HttpSecurity
              • Example
            • AuthenticationManager
            • AuthenticationProvider
            • UserDetailsService
              • UserDetails
              • PasswordEncoder
            • SecurityContext
            • SecurityContextHolder
            • GrantedAuthority
            • Security Configuration (Spring Security DSL)
          • Authentication Models
            • One-Way Authentication
            • Mutual Authentication
          • Authentication Mechanism
            • Basic Authentication
            • Form-Based Authentication
            • Token-Based Authentication (JWT)
            • OAuth2 Authentication
            • Multi-Factor Authentication (MFA)
            • SAML Authentication
            • X.509 Certificate Authentication
            • API Key Authentication
            • Remember-Me Authentication
            • Custom Authentication
          • Logout Handling
        • Authorization
        • Security Filters and Interceptors
        • CSRF
          • Real-World CSRF Attacks & Prevention
        • CORS
        • Session Management and Security
        • Best Practices
      • Spring Persistence
        • JDBC
          • JDBC Components
          • JDBC Template
          • Transaction Management
          • Best Practices in JDBC Usage
          • Datasource
            • Connection Pooling
              • HikariCP
            • Caching
        • JPA (Java Persistence API)
          • JPA Fundamentals
          • ORM Mapping Annotations
            • 1. Entity and Table Mappings
            • 2. Field/Column Mappings
            • 3. Relationship Mappings
            • 4. Inheritance Mappings
            • 5. Additional Configuration Annotations
          • Querying Data
            • JPQL
            • Criteria API
            • JPA Specification
              • Example - Employee Portal
            • Native SQL Queries
            • Named Queries
            • Query Return Types
            • Pagination & Sorting
              • Example - Employee Portal
            • Projection
          • Fetch Strategies in JPA
        • JPA Implementation
          • Hibernate
            • Properties
            • Example
        • Spring Data JPA
          • Repository Abstractions
          • Entity-to-Table Mapping
          • Derived Query Methods
        • Cross-Cutting Concerns
          • Transactions
          • Caching
          • Concurrency
        • Examples
          • Employee Portal
            • API
    • Distributed Systems & Communication
      • Distributed Scheduling
      • Inter-Service Communication
        • 1. RestTemplate
        • 2. WebClient
        • 3. OpenFeign
        • Retry Mechanism
          • @Retryable annotation
            • Example
    • Security & Data Protection
      • Encoding | Decoding
        • Types
          • Base Encoding
            • Base16 - TBD
              • Encoding and Decoding in Java - TBD
            • Base32
              • Encoding and Decoding in Java
            • Base64 -TBD
              • Encoding and Decoding in Java - TBD
          • Text Encoding - TBD
            • Extended ASCII
              • Encoding and Decoding in Java - TBD
                • ISO-8859-1
                • Windows-1252 - TBD
                • IBM Code Pages - TBD
            • ASCII
              • Encoding and Decoding in Java
        • Java Guidelines
          • Text Encoding Decoding Examples
          • Base Encoding Decoding Examples
          • Best Practices and Concepts
          • Libraries
      • Cryptography
        • Terminology
        • Java Cryptography Architecture (JCA)
        • Key Management
          • Key Generation
            • Tools and Libraries
              • OpenSSL
              • Java Keytool
                • Concept
                • Use Cases
            • Key & Certificate File Formats
          • Key Distribution
          • Key Storage
          • Key Rotation
          • Key Revocation
        • Encryption & Decryption
          • Symmetric Encryption
            • Algorithm
            • Modes of Operation
            • Examples
          • Asymmetric Encryption
            • Algorithm
            • Mode of Operation
            • Examples
    • Utilities & Libraries
      • Apache Libraries
        • Apache Camel
          • Camel Architecture
            • Camel Context
            • Camel Endpoints
            • Camel Components
            • Camel Exchange & MEP
          • Spring Dependency
          • Different Components
            • Camel SFTP
        • Apache Commons Lang
      • MapStruct Mapper
      • Utilities by Spring framework
        • FileCopyUtils
    • General Concepts
      • Spring Boot Artifact Packaging
      • Classpath and Resource Loading
      • Configuration - Mapping Properties to Java Class
      • Validations in Spring Framework
        • Jakarta Validation
          • Jakarta Bean Validation Annotations
    • Practical Guidelines
      • Spring Configuration
      • Spring Code Design
  • Software Testing
    • Software Testing Methodologies
      • Functional Testing
      • Non Functional Testing
    • Software Testing Life Cycle (STLC)
    • Integration Test
      • Dynamic Property Registration
    • Java Test Framework
      • JUnit
        • JUnit 4
          • Examples
        • JUnit 5
          • Examples
        • JUnit 4 vs JUnit 5
  • System Design
    • Foundations
      • Programming Paradigms
      • Object-Oriented Design
        • SOLID Principles
        • GRASP Principles
        • Composition
        • Aggregation
        • Association
      • Design Pattern
        • Creational Pattern
        • Structural Pattern
        • Behavioral Pattern
        • Examples
          • Data Collector
          • Payment Processor
        • Design Enhancements
          • Fluent API Design
            • Examples
    • Architectural Building Blocks
      • CAP Theorem
      • Load Balancer
        • Load Balancer Architecture
        • Load Balancing in Java Microservices
          • Client-Side Load Balancing Example
          • Server-Side Load Balancing Example
        • Load Balancer Monitoring Tool
      • Scaling
        • Vertical Scaling (Scaling Up)
        • Horizontal Scaling (Scaling Out)
        • Auto-Scaling
        • Database Scaling via Sharding
      • Caching
        • Pod-Level vs Distributed Caching
      • Networking Metrics
        • Types of Delay
        • Scenario
      • System Characteristics
      • Workload Types
      • Resilience & Failure Handling
    • Performance
      • Why Is My API Sometimes Slow ?
    • Security
      • Security by Design
      • Zero Trust Security Model
      • Zero Trust Architecture
      • Principles
        • CIA
        • Least Privilege Principle
        • Defense in Depth
      • Security Threats & Mitigations
        • OWASP
          • Top 10 Security Threats
          • Application Security Verification Standard
          • Software Assurance Maturity Model
          • Dependency Check
          • CSRFGuard
          • Cheat Sheets
          • Security Testing Guide
          • Threat Dragon
        • Threat Modeling
      • Compliance & Regulation
        • PCI DSS
    • Deployment Patterns
    • Diagrams
      • UML Diagrams
        • PlantUML
          • Class Diagram
          • Object Diagram
          • Sequence Diagram
          • Use Case Diagram
          • Activity Diagram
          • State Diagram
          • Architecture Diagram
          • Component Diagram
          • Timing Diagram
          • ER Diagram (Entity-Relationship)
          • Network Diagram
    • Common Terminologies
    • Problems
      • Reference Materials
      • Cache Design
  • Interview Guide
    • Non-Technical
      • Behavioural or Introductory Guide
      • Project Specific
    • Technical
      • Java Interview Companion
        • Java Key Concepts
          • Set 1
          • Set 2
        • Java Code Snippets
        • Java Practice Programs
          • Set 3 - Strings
          • Set 4 - Search
          • Set 5 - Streams and Collection
      • SQL Interview Companion
        • SQL Practice Problems
          • Set 1
      • Spring Interview Companion
        • Spring Key Concepts
          • Set 1 - General
          • Set 2 - Core Spring
        • Spring Code Snippets
          • JPA
      • Application Server
      • Maven
      • Containerized Application
      • Microservices
    • General
      • Applicant Tracking System (ATS)
      • Flowchart - How to Solve Coding Problem?
Powered by GitBook
On this page
  • Description
  • Types of Creational Pattern
  • Singleton Pattern
  • Factory Method Pattern
  • Abstract Factory Pattern
  • Builder Pattern
  • Prototype Pattern

Was this helpful?

  1. System Design
  2. Foundations
  3. Design Pattern

Creational Pattern

Description

Creational patterns in software engineering deal with the process of object creation in a flexible and reusable manner. They aim to provide mechanisms to instantiate objects in various situations while promoting loose coupling between the creator and the created objects. Creational patterns often involve hiding the object creation logic and making the system more independent of how its objects are created, composed, and represented

Types of Creational Pattern

Singleton Pattern

Description

The Singleton Pattern ensures a class has only one instance and provides a global point of access to that instance. It involves creating a class with a method that returns the same instance of the class every time it's called, thus restricting the instantiation of the class to a single object. It's like having a single source of truth for a specific concept within your application.

Imagine a scenario where we only need one instance of a class throughout the application. For example, a system might have a single Logger object to handle all logging activities, or a configuration manager to hold all application settings. The Singleton Pattern ensures this by:

  1. Restricting object creation: The constructor of the Singleton class is typically private or protected, preventing direct instantiation from outside the class.

  2. Providing a static method: A public static method, often named getInstance(), is used to access the single instance. If the instance doesn't exist, it's created on the first call and returned. Subsequent calls reuse the same instance.

Benefits of Singleton Pattern

  • Global access point: Provides a centralized and consistent way to access the single instance.

  • Resource management: Ensures only one instance exists, which can be helpful for managing resources like file handles or database connections.

  • Enforces single instance: Guarantees that there's only one instance of the class, preventing conflicts or inconsistencies.

Drawbacks of Singleton Pattern

  • Tight coupling: Code that relies on the Singleton class becomes tightly coupled to it, making testing and refactoring more challenging.

  • Global state: The Singleton instance holds global state, which can be difficult to manage and reason about in complex applications.

  • Limited flexibility: It might be difficult to create multiple instances for testing or special scenarios.

Example

public class Logger {
    // Private static instance variable
    private static Logger instance;
    
    // Private constructor to prevent instantiation
    private Logger() {
        // Initialization logic
    }
    
    // Public static method to get the singleton instance
    public static Logger getInstance() {
        // Lazy initialization: Create the instance if it doesn't exist
        if (instance == null) {
            instance = new Logger();
        }
        return instance;
    }
    
    // Public method to log messages
    public void log(String message) {
        System.out.println("Logging: " + message);
        // Additional logging logic
    }
}

In a Spring Boot application, the Singleton pattern is utilized extensively due to the nature of Spring's bean management. By default, Spring manages beans as singletons within the container, meaning that only one instance of a bean is created per container context.

Example: UserService in a Spring Boot Application

Suppose we have a UserService class responsible for managing user-related operations, such as fetching user data from a database, performing user authentication, and so on. We want to ensure that there's only one instance of UserService throughout the application, so we annotate it with @Service (or @Component) to let Spring manage it as a singleton:

import org.springframework.stereotype.Service;

@Service
public class UserService {

    public void fetchUserData() {
        // Code to fetch user data from the database
        System.out.println("Fetching user data...");
    }

    public void authenticateUser() {
        // Code to authenticate user
        System.out.println("Authenticating user...");
    }
}

By default, Spring will create a single instance of UserService and manage it as a singleton bean within the application context. Let's use the UserService in a controller. We inject the UserService dependency using constructor injection. When the Spring Boot application starts up, Spring creates a single instance of UserService and manages it as a singleton bean within the application context. Whenever the UserController is instantiated, it receives the same instance of UserService. This ensures that all requests to /userdata endpoint is handled by the same instance of UserService, maintaining consistency and preventing unnecessary object creation.

package org.example.api;

import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RequiredArgsConstructor
@RestController
public class UserController {

    private final UserService userService;

    @GetMapping("/userdata")
    public String getUserData() {
        userService.fetchUserData();
        return "User data fetched successfully!";
    }
}

Factory Method Pattern

Description

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. It defines an interface for creating an object, but subclasses are responsible for instantiating the appropriate concrete class. This pattern promotes loose coupling between the creator and the created objects, allowing the creator to defer object creation to its subclasses.

Imagine a scenario where you need to create different types of objects based on certain conditions, but you want to keep the client code (code that uses the objects) decoupled from the specific object creation logic. The Factory Method Pattern achieves this by:

  1. Defining a Factory Interface: This interface declares a method (often named createProduct()) that all concrete factory classes must implement. This method returns an instance of a product (the object to be created).

  2. Creating Concrete Factory Classes: These classes implement the FactoryInterface and provide specific implementations for the createProduct() method. Each concrete factory creates a specific type of product.

  3. Client Code Uses Factory: The client code interacts with a factory object (often obtained through a static method or dependency injection) and calls the createProduct() method to get the desired product instance. It doesn't need to know the concrete class of the product being created.

Benefits of Factory Method Pattern

  • Decouples object creation: Separates object creation logic from the client code, promoting loose coupling.

  • Flexibility in object creation: Allows for creating different types of objects based on runtime conditions.

  • Open/Closed Principle compliance: New concrete factories can be added without modifying existing client code.

Drawbacks of Factory Method Pattern

  • Introduces an extra layer of abstraction: The factory classes add complexity compared to direct object creation.

  • Might not be necessary for simple scenarios: If only a few object types exist, simpler approaches might be sufficient.

Example 1

// Interface for creating Pizzas
public interface PizzaFactory {
  Pizza createPizza(String type);
}

// Concrete Factory Class for Margherita Pizza
public class MargheritaPizzaFactory implements PizzaFactory {
  @Override
  public Pizza createPizza(String type) {
    if (type.equals("Margherita")) {
      return new MargheritaPizza();
    } else {
      throw new IllegalArgumentException("Invalid pizza type");
    }
  }
}

// Concrete Factory Class for Hawaiian Pizza (similar structure)
public class HawaiianPizzaFactory implements PizzaFactory {
  @Override
  public Pizza createPizza(String type) {
    // ... (implementation to create Hawaiian Pizza)
  }
}

// Client Code (doesn't know concrete Pizza classes)
public class PizzaOrder {
  public Pizza orderPizza(String type, PizzaFactory factory) {
    return factory.createPizza(type);
  }
}

public class Main {
  public static void main(String[] args) {
    PizzaOrder order = new PizzaOrder();
    PizzaFactory factory = new MargheritaPizzaFactory(); // Choose factory
    Pizza pizza = order.orderPizza("Margherita", factory);
    // ... (use the pizza)
  }
}

Example 2

Create a Shape interface and concrete classes implementing the Shape interface.

// Shape interface
package src.main.java.shape;

public interface Shape {
    public void draw();
}


// Circle class
package src.main.java.shape;

public class Circle implements Shape{
    @Override
    public void draw() {
        System.out.println("Drawing Circle");
    }
}


// Square class
package src.main.java.shape;

public class Square implements Shape{
    @Override
    public void draw() {
        System.out.println("Drawing Square");
    }
}


// Rectangle class
package src.main.java.shape;

public class Rectangle implements Shape{
    @Override
    public void draw() {
        System.out.println("Drawing Rectangle");
    }
}


// ShapeFactory
package src.main.java.shape;

public class ShapeFactory {
    public static Shape getShape(String shapeType) {
        return switch (shapeType) {
            case "CIRCLE" -> new Circle();
            case "SQUARE" -> new Square();
            case "RECTANGLE" -> new Rectangle();
            default -> null;
        };
    }
}


// Main Application class
package src.main.java;

import src.main.java.shape.Shape;
import src.main.java.shape.ShapeFactory;

public class Application {
    public static void main(String[] args) {
        Shape shape1 = ShapeFactory.getShape("SQUARE");
        shape1.draw();

        Shape shape2 = ShapeFactory.getShape("CIRCLE");
        shape2.draw();

        Shape shape3 = ShapeFactory.getShape("RECTANGLE");
        shape3.draw();
    }
}

The Factory Method Pattern allows for flexible object creation by delegating the responsibility of creating objects to subclasses, making the system more extensible and maintainable. Each subclass can determine the type of object to create based on its specific requirements.

Abstract Factory Pattern

The Abstract Factory Pattern is a creational design pattern that builds upon the Factory Method Pattern. It provides a way to create families of related objects without specifying their concrete types. The Abstract Factory Pattern is useful when we need to create multiple families of related objects or when we want to provide a level of abstraction for creating objects.

Imagine we have a scenario where we need to create multiple objects that belong to a specific family (e.g., a family of shapes like circle, square, or a family of UI elements like button, checkbox). The Abstract Factory Pattern helps create these families without exposing the concrete implementation details. Here's the approach:

  • Defining an Abstract Factory Interface: This interface declares methods for creating each product within the family (e.g., createShape() and createColor() for shapes and colors).

  • Creating Concrete Factory Classes: These classes implement the AbstractFactoryInterface and provide specific implementations for creating each product in the family. Each concrete factory creates a related set of products (e.g., a ShapeFactory might create Circle and Square, while a UIFactory might create Button and Checkbox).

  • Client Code Uses Factory: The client code interacts with a concrete factory object (often obtained through a static method or dependency injection) and calls the specific create methods to get the desired product instances. Similar to the Factory Method Pattern, the client doesn't need to know the concrete class of the products being created.

Benefits of Abstract Factory Pattern

  • Creates families of objects: Ensures consistent creation of related objects within a family.

  • Decouples object creation: Separates object creation logic from the client code, promoting loose coupling.

  • Flexibility in product types: Allows for creating different families of objects (shapes and colors, UI elements and data providers) without modifying existing code.

Drawbacks of Abstract Factory Pattern

  • More complex: Introduces additional abstraction compared to the Factory Method Pattern.

  • Might be overkill for simple scenarios: If you only need to create a few unrelated objects, simpler approaches might be more suitable.

Example 1

Consider a drawing application with shapes (circle, square) and colors (red, green). The Abstract Factory Pattern can be used to create these objects together. The ShapeColorFactory interface defines methods for creating shapes and colors. Concrete factories (CircleRedFactory, etc.) implement the interface and create related shapes and colors. The client code (Drawing) uses a factory object to get both a shape and a color without knowing their concrete classes.

// Abstract Factory Interface for creating Shapes and Colors
public interface ShapeColorFactory {
  Shape createShape(String type);
  Color createColor(String type);
}

// Concrete Factory Class for creating Circle and Red objects
public class CircleRedFactory implements ShapeColorFactory {
  @Override
  public Shape createShape(String type) {
    if (type.equals("Circle")) {
      return new Circle();
    } else {
      throw new IllegalArgumentException("Invalid shape type");
    }
  }

  @Override
  public Color createColor(String type) {
    if (type.equals("Red")) {
      return new Red();
    } else {
      throw new IllegalArgumentException("Invalid color type");
    }
  }
}

// Concrete Factory Class for creating Square and Green objects (similar structure)
public class SquareGreenFactory implements ShapeColorFactory {
  // ... (implementations for creating Square and Green)
}

// Client Code (doesn't know concrete Shape or Color classes)
public class Drawing {
  public void draw(String shapeType, String colorType, ShapeColorFactory factory) {
    Shape shape = factory.createShape(shapeType);
    Color color = factory.createColor(colorType);
    // ... (use the shape and color for drawing)
  }
}

public class Main {
  public static void main(String[] args) {
    Drawing drawing = new Drawing();
    ShapeColorFactory factory = new CircleRedFactory(); // Choose factory
    drawing.draw("Circle", "Red", factory);
    // ... (draw other shapes and colors using different factories)
  }
}

Example 2

We have abstract product interfaces Button and TextField representing UI components. And concrete product classes LightThemeButton, DarkThemeButton, LightThemeTextField, and DarkThemeTextField that implement the abstract product interfaces with specific implementations for each theme. We have an abstract factory interface GUIFactory with factory methods createButton() and createTextField() for creating UI components. Also, we created concrete factory classes LightThemeFactory and DarkThemeFactory that implement the abstract factory interface and provide implementations for creating UI components for the Light Theme and Dark Theme, respectively.

We create instances of LightThemeFactory and DarkThemeFactory to represent factories for creating UI components for the Light Theme and Dark Theme, respectively. We use the factory methods createButton() and createTextField() to create UI components for the respective themes. The concrete factory classes (LightThemeFactory and DarkThemeFactory) internally handle the creation of UI components based on the specific theme.

// Abstract Product A: Button
interface Button {
    void paint();
}

// Concrete Products A1: LightThemeButton
class LightThemeButton implements Button {
    @Override
    public void paint() {
        System.out.println("Rendering button in Light Theme");
        // Rendering logic for button in Light Theme
    }
}

// Concrete Products A2: DarkThemeButton
class DarkThemeButton implements Button {
    @Override
    public void paint() {
        System.out.println("Rendering button in Dark Theme");
        // Rendering logic for button in Dark Theme
    }
}

// Abstract Product B: TextField
interface TextField {
    void paint();
}

// Concrete Products B1: LightThemeTextField
class LightThemeTextField implements TextField {
    @Override
    public void paint() {
        System.out.println("Rendering text field in Light Theme");
        // Rendering logic for text field in Light Theme
    }
}

// Concrete Products B2: DarkThemeTextField
class DarkThemeTextField implements TextField {
    @Override
    public void paint() {
        System.out.println("Rendering text field in Dark Theme");
        // Rendering logic for text field in Dark Theme
    }
}

// Abstract Factory: GUIFactory
interface GUIFactory {
    Button createButton();
    TextField createTextField();
}

// Concrete Factories: LightThemeFactory and DarkThemeFactory
class LightThemeFactory implements GUIFactory {
    @Override
    public Button createButton() {
        return new LightThemeButton();
    }

    @Override
    public TextField createTextField() {
        return new LightThemeTextField();
    }
}

class DarkThemeFactory implements GUIFactory {
    @Override
    public Button createButton() {
        return new DarkThemeButton();
    }

    @Override
    public TextField createTextField() {
        return new DarkThemeTextField();
    }
}

public class Application {
    public static void main(String[] args) {
        // Create a Light Theme GUI
        GUIFactory lightThemeFactory = new LightThemeFactory();
        Button lightThemeButton = lightThemeFactory.createButton();
        TextField lightThemeTextField = lightThemeFactory.createTextField();

        // Render UI components
        lightThemeButton.paint();
        lightThemeTextField.paint();
        
        // Create a Dark Theme GUI
        GUIFactory darkThemeFactory = new DarkThemeFactory();
        Button darkThemeButton = darkThemeFactory.createButton();
        TextField darkThemeTextField = darkThemeFactory.createTextField();

        // Render UI components
        darkThemeButton.paint();
        darkThemeTextField.paint();
    }
}

Difference between Factory Method Pattern and Abstract Factory Pattern

Feature
Factory Method Pattern
Abstract Factory Pattern

Focus

Single type or related types

Families of related objects

Structure

Factory interface + Concrete Factories

Abstract Factory + Concrete Factories

Client Interaction

Calls factory to get a product

Calls factory to get products from a family

Benefits

Decoupling, Flexibility

Consistency, Flexibility, Multiple Families

Number of Methods

Typically includes a single factory method for creating a single type of object.

Typically includes multiple factory methods for creating different types of related objects.

Drawbacks

Extra abstraction, Simple cases

More complex, Overkill for simple cases

Builder Pattern

Description

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 useful when you need to create an object with many optional parameters or a complex construction process, and you want to keep the construction logic separate from the actual object construction. The Builder Pattern promotes fluent and readable object creation code by providing a step-by-step approach to constructing objects.

Imagine if we have a complex object with many optional parameters. Creating such objects directly with a large constructor can be cumbersome and error-prone. The Builder Pattern provides a solution by:

  1. Defining a Builder Class: This class has methods for setting optional and mandatory properties of the object being constructed. Each method typically returns the builder object itself, allowing for method chaining (calling multiple builder methods consecutively).

  2. Building the Object: The builder class also provides a build() method that takes the partially constructed object and returns the final, immutable object.

Example

A computer can have various optional components, such as a CPU, RAM, storage, graphics card, etc. We can use the Builder Pattern to create a ComputerBuilder class with methods for configuring each component, and a Computer class to represent the final constructed computer. The build() method constructs and returns the final Computer object using the specified components.

class Computer {
    private String cpu;
    private String ram;
    private String storage;
    private String graphicsCard;

    public Computer(String cpu, String ram, String storage, String graphicsCard) {
        this.cpu = cpu;
        this.ram = ram;
        this.storage = storage;
        this.graphicsCard = graphicsCard;
    }

    // Getters for computer components
    // ...
}

// Builder: ComputerBuilder
class ComputerBuilder {
    private String cpu;
    private String ram;
    private String storage;
    private String graphicsCard;

    public ComputerBuilder withCPU(String cpu) {
        this.cpu = cpu;
        return this;
    }

    public ComputerBuilder withRAM(String ram) {
        this.ram = ram;
        return this;
    }

    public ComputerBuilder withStorage(String storage) {
        this.storage = storage;
        return this;
    }

    public ComputerBuilder withGraphicsCard(String graphicsCard) {
        this.graphicsCard = graphicsCard;
        return this;
    }

    public Computer build() {
        return new Computer(cpu, ram, storage, graphicsCard);
    }
}

// Main Application class
public class Application {
    public static void main(String[] args) {
        Computer computer = new ComputerBuilder()
                .withCPU("Intel Core i7")
                .withRAM("16GB DDR4")
                .withStorage("512GB SSD")
                .withGraphicsCard("NVIDIA GeForce RTX 3080")
                .build();
    }
}

Lombok provides an @Builder annotation that can be used to generate a builder class for a given class, eliminating the need to manually write the builder class.

// Computer class
import lombok.Builder;
import lombok.Getter;

@Getter
@Builder
class Computer {
    private String cpu;
    private String ram;
    private String storage;
    private String graphicsCard;
}

// Main Application class
public class Application {
    public static void main(String[] args) {
        Computer computer = Computer.builder()
                .cpu("Intel Core i7")
                .ram("16GB DDR4")
                .storage("512GB SSD")
                .graphicsCard("NVIDIA GeForce RTX 3080")
                .build();
    }
}

By using Lombok's @Builder annotation, we can achieve the same functionality as the manually written builder class with less code. Lombok takes care of generating the builder class and its methods, reducing the need for boilerplate code. This makes the code cleaner, more concise, and easier to maintain.

Prototype Pattern

Description

The Prototype Pattern is a creational design pattern that allows to create new objects by copying an existing object, known as the prototype, instead of creating new instances from scratch. It is useful when the construction of new objects is more expensive or complex, and you want to avoid the overhead of repeated object creation by reusing existing instances. The Prototype Pattern promotes object creation by cloning existing objects, providing a convenient and efficient way to create new objects with similar properties.

Imagine if we have an object with a lot of internal data or complex initialization logic. Creating new instances of this object can be expensive or time-consuming. The Prototype Pattern addresses this by:

  1. Defining an Interface (Optional): An interface (often named Cloneable) can be used to mark classes that support cloning. However, Java doesn't enforce interfaces for implementing the pattern.

  2. Implementing the clone() Method: The class representing the prototype object implements the clone() method. This method typically performs a shallow copy of the object's state, creating a new object with the same values but independent references to mutable member variables.

Benefits of Prototype Pattern

  • Improved performance: Reduces the cost of creating new objects, especially for complex objects.

  • Reduces memory usage: By reusing existing objects, the prototype pattern can help optimize memory usage.

  • Customization: You can modify the cloned object after creation to achieve variations.

Drawbacks of Prototype Pattern

  • Shallow copy issues: Be mindful of shallow copying and handle references to mutable objects within the prototype appropriately (e.g., by deep copying them).

  • Complexity for mutable objects: Managing the state of mutable objects within the prototype requires careful consideration.

  • Limited use cases: Not all objects are suitable for cloning. The pattern might be unnecessary for simple objects.

When to Use Prototype Pattern

The Prototype Pattern is suitable when:

  • We need to create multiple copies of an object that is expensive or complex to create.

  • We want to customize newly created objects based on an existing one.

Example 1

Let's consider a example of a document editor application. Users can create documents with various formatting styles, such as font size, font style, and alignment. We can use the Prototype Pattern to create a prototype document object and then clone it to create new documents with similar properties.

We have a prototype class Document representing a document with content and formatting styles. The Document class implements the Cloneable interface to indicate that it supports cloning. We override the clone() method to create a deep copy of the Document object, including its content and styles. We then modify the content and styles of the new documents independently without affecting the prototype or other clones.

// Document class
import java.util.ArrayList;
import java.util.List;

class Document implements Cloneable {
    private String content;
    private List<String> styles;

    public Document(String content, List<String> styles) {
        this.content = content;
        this.styles = styles;
    }

    // Getter methods for content and styles

    @Override
    public Document clone() throws CloneNotSupportedException {
        List<String> clonedStyles = new ArrayList<>(this.styles);
        return new Document(this.content, clonedStyles);
    }
}

// Main Application class
public class Application {
    public static void main(String[] args) throws CloneNotSupportedException {
        // Create a prototype document
        Document prototypeDocument = new Document("Prototype Document", List.of("Bold", "Italic"));

        // Clone the prototype to create new documents
        Document document1 = prototypeDocument.clone();
        Document document2 = prototypeDocument.clone();

        // Modify the content and styles of new documents if needed
        document1.setContent("Document 1 Content");
        document1.getStyles().add("Underline");

        document2.setContent("Document 2 Content");
        document2.getStyles().remove("Bold");

        // Print the details of new documents
        System.out.println("Document 1: " + document1.getContent() + ", Styles: " + document1.getStyles());
        System.out.println("Document 2: " + document2.getContent() + ", Styles: " + document2.getStyles());
    }
}

Example 2

Consider a game where we need to create multiple enemies of the same type (e.g., Orcs). Instead of creating each Orc from scratch, you can use a prototype.

The Orc class implements Cloneable (optional). The clone() method creates a new Orc object with the same values as the prototype. The clone() method typically performs a shallow copy, meaning references to objects within the prototype (like the Weapon here) are also copied by reference, not by value.

// Orc class
public class Orc implements Cloneable { // Implements Cloneable (optional)
  private String name;
  private int health;
  private Weapon weapon; // Reference to a Weapon object

  public Orc(String name, int health, Weapon weapon) {
    this.name = name;
    this.health = health;
    this.weapon = weapon;
  }

  @Override
  public Object clone() throws CloneNotSupportedException {
    Orc clone = new Orc(name, health, weapon); // Shallow copy
    return clone;
  }
}

// Game class which create multiple cloned objects
public class Game {
  public void spawnEnemies() throws CloneNotSupportedException {
    Orc prototype = new Orc("Default Orc", 100, new Weapon("Axe"));
    Orc enemy1 = (Orc) prototype.clone();
    Orc enemy2 = (Orc) prototype.clone();
    // ... (use enemy1 and enemy2 in the game)
  }
}
PreviousDesign PatternNextStructural Pattern

Last updated 8 months ago

Was this helpful?