Define an interface for creating an object, but let subclasses decide which class to instantiate. Factory Method lets a class defer instantiation to subclasses.

  1. In Factory Pattern we have Product(Abstract), ConcreteProduct and Creator(Abstract), ConcreteCreator
  2. ConcreteCreator would create ConcreteProduct by implementing abstract factory method of Creator which has Product return type
  3. Incase if there is any new Product to be added it fully supports Open Closed Principle(Open For Extension, Closed for Changes).
  4. Open for Extension – Adding new ConcreteProduct and ConcreateCreator class, Closed for Changes – No changes in anyother code unlike Simple factory or static factory method which requires change in Switchcase, enum (or) if case
  5. Closed for Changes – No changes in anyother code unlike Simple factory or static factory method which requires change in Switchcase, enum (or) if case

JobProfile.java

abstract class JobProfile {
    public abstract String mandatorySkills();

    public Integer defaultWorkHrs(){
        return 8;
    }
}

JavaProfile.java

public class JavaProfile extends JobProfile{
    @Override
    public String mandatorySkills() {
        return "Java, Springboot, Microservices";
    }
}

SQLProfile.java

public class SQLProfile extends JobProfile{
    @Override
    public String mandatorySkills() {
        return "Cosmos, MySQL, MSSQL";
    }
}

JobProfileCreator.java

abstract class JobProfileCreator {
    public JobProfile getJobProfile(){
        JobProfile objJobProfile = createJobProfileFactory();
        return objJobProfile;
    }

    public abstract JobProfile createJobProfileFactory();
}

JavaProfileCreator.java

public class JavaProfileCreator extends JobProfileCreator {
    @Override
    public JobProfile createJobProfileFactory() {
       return new JavaProfile();
    }
}

SQLProfileCreator.java

public class SQLProfileCreator extends JobProfileCreator {
    @Override
    public JobProfile createJobProfileFactory() {
        return new SQLProfile();
    }
}

Consultancy.java

public class Consultancy {
    public static void main(String[] args) {
        getProfileDetails(new JavaProfileCreator());
    }

    public static void getProfileDetails(JobProfileCreator jobProfileCreator){
        JobProfile objJobProfile = jobProfileCreator.getJobProfile();
        System.out.println(objJobProfile.mandatorySkills() + " with "+ objJobProfile.defaultWorkHrs() + "hrs of Work");
    }
}

Output

Java, Springboot, Microservices with 8hrs of Work

  1. In Simple Factory we have a Factory Class(LoggerFactory.java) and We call the createLogger method which returns different implementation of logger
  2. Logger is a abstract class which has different implementations

Logger.java

public abstract class Logger {
  abstract void log(String logstring);
}

ConsoleLogger.java

public class ConsoleLogger extends Logger{
    @Override
    void log(String logstring) {
        System.out.println("Logging to Console - "+ logstring);
    }
}

DBLogger.java

public class DBLogger extends Logger{
    @Override
    void log(String logstring) {
        System.out.println("Logging to Database - "+ logstring);
    }
}

FileLogger.java

public class FileLogger extends Logger{
    @Override
    void log(String logstring) {
        System.out.println("Logging to File - "+ logstring);
    }
}

LoggerFactory.java

public class LoggerFactory {
    public enum LoggerType {
        DATABASE, FILE, CONSOLE;
    }

    //The same code could be written using if else block instead of switch case
    public Logger createLogger(LoggerType loggerType) {
        Logger logger;

        switch (loggerType) {
            case FILE:
                logger = new FileLogger();
                break;
            case DATABASE:
                logger = new DBLogger();
                break;
            case CONSOLE:
                logger = new ConsoleLogger();
                break;
            default:
                logger = new ConsoleLogger();
                break;
        }

        return logger;
    }
}

ClientApp.java

public class ClientApp {
    public static void main(String[] args) {
        LoggerFactory objLoggerFactory = new LoggerFactory();
        Logger logger = objLoggerFactory.createLogger(LoggerFactory.LoggerType.CONSOLE);
        logger.log("Hello there");
    }
}

Output

Logging to Console - Hello there

Factory allows the consumer to create new objects without having to know the details of how they’re created, or what their dependencies are – they only have to give the information they actually want.

Account.java

abstract class Account {
   abstract Integer calculateInterest();
}

CreditAccount.java

public class CreditAccount extends Account{
    @Override
    Integer calculateInterest() {
        return 11;
    }
}

SalaryAccount.java

public class SalaryAccount extends Account{
    @Override
    Integer calculateInterest() {
        return 5;
    }
}

SavingsAccount.java

public class SavingsAccount extends Account{
   @Override
    Integer calculateInterest() {
        return 7;
    }
}

AccountFactory.java

public class AccountFactory {
    static Map<String, Account>  hmAccountMap =  new HashMap<>();

    static {
        hmAccountMap.put("SavingsAcc", new SavingsAccount());
        hmAccountMap.put("CreditAcc", new CreditAccount());
        hmAccountMap.put("SalaryAcc", new SalaryAccount());
    }

    public static Account getAccount(String accountType){
        return hmAccountMap.get(accountType);
    }
}

CalcInterest.java

public class CalcInterest{
    public static void main(String[] args) {
        Account objAccountFactory = AccountFactory.getAccount("SavingsAcc");
        System.out.println("Interest rate is - " + Optional.of(objAccountFactory.calculateInterest()));
    }
}

Using Streams for AccountFactory Class
AccountFactory.java

public static Optional<Account> getAccount(String accountType) {
        return hmAccountMap.entrySet().stream()
                                      .filter(accParam -> accParam.getKey().equals(accountType))
                                      .findFirst()
                                      .map(Map.Entry::getValue);
}

CalcInterest.java

public class CalcInterest{
    public static void main(String[] args) {
        Account objAccountFactory = AccountFactory.getAccount("SavingsAcc");
        System.out.println("Interest rate is - " + Optional.of(objAccountFactory.calculateInterest()));
    }
}

Output

Interest rate is - 7

Static factory method is a static method that returns an instance of a class. The key idea is to gain control over object creation and delegate it from constructor to static method.

The key idea of static factory method is to gain control over object creation and delegate it from constructor to static method. The decision of object to be created is like in Factory made outside the method (in common case, but not always). While the key (!) idea of Factory Method is to delegate decision of what instance of class to create inside Factory Method. E.g. classic Singleton implementation is a special case of static factory method. Example of commonly used static factory methods:

  • valueOf
  • getInstance(used in singleton)
  • newInstance

When to use?

  1. Static factory methods can have meaningful names, hence explicitly conveying what they do
  2. Static factory methods can return the same type that implements the method(s), a subtype, and also primitives, so they offer a more flexible range of returning types
  3. Static factory methods can encapsulate all the logic required for pre-constructing fully initialized instances, so they can be used for moving this additional logic out of constructors. This prevents constructors from performing further tasks, others than just initializing fields

Another example of static factory is as follow

Optional<String> value1 = Optional.empty();
Optional<String> value2 = Optional.of("Baeldung");
Optional<String> value3 = Optional.ofNullable(null);

Logger.java

public class Logger {
    public String logType;
    public String fileLocation;

    public Logger(String logType) {
        this.logType = logType;
    }

    public Logger() {
    }

    public static Logger getDefaultLogger(){
        return new Logger("Console");
    }

    public static Logger getFileLogger(String fileLocation){
        Logger logger =  new Logger("File");
        logger.fileLocation = fileLocation;
        return logger;
    }

    @Override
    public String toString() {
        return "Logger{" +
                "logType=" + logType +
                ", fileLocation='" + fileLocation + '\'' +
                '}';
    }
}

ClientApp.java

public class ClientApp {
    public static void main(String[] args) {
        Logger objLogger = Logger.getDefaultLogger();
        System.out.println(objLogger);
    }
}

Output

Logger{logType=Console, fileLocation='null'}

Logger.java

public class Logger {
    public String logType;
    public String fileLocation;

    public Logger(String logType) {
        this.logType = logType;
    }

    public Logger() {
    }

    public static Logger getLoggerInstance(LoggerType loggerType){
        Logger logger;
        switch(loggerType) {
            case CONSOLE:
                logger = new Logger("Console Logger");
                logger.fileLocation = "JVM Memory";
                break;
            case DATABASE:
                logger = new Logger("Database Logger");
                logger.fileLocation = "DB Connection";
                break;
            case FILE:
                logger = new Logger("File Logger");
                logger.fileLocation = "C:/logs";
                break;
            case SPLUNK:
                logger = new Logger("Splunk Logger");
                logger.fileLocation = "Splunk URL";
                break;
            default:
                logger = new Logger();

        }

        return logger;
    }

    @Override
    public String toString() {
        return "Logger{" +
                "logType=" + logType +
                ", fileLocation='" + fileLocation + '\'' +
                '}';
    }
}

ClientApp.java

public static void main(String[] args) {
        Logger objLogger = Logger.getLoggerInstance(LoggerType.CONSOLE);
        System.out.println(objLogger);
    }

LoggerType.java

public enum LoggerType {
    CONSOLE, FILE, DATABASE, SPLUNK
}

Output

Logger{logType=Console Logger, fileLocation='JVM Memory'}

Simple Factory and Static Factory are not design patterns rather coding style. However factory pattern could be implemented by using any one of the coding style but they wont honor Open Closed Principle.

As per Open Closed Principle, Code should be open to extension and Closed to Changes. In both the above coding style we have to change either the if else or switch case if new implementation of abstract class is introduces.

Factory Pattern is a design pattern in object-oriented programming that lets subclasses decide which class to instantiate when creating an object

Now Factory pattern honors Open Closed Principle and does not need change to factory method

Factory Pattern vs Abstract Factory Pattern
One difference between the two is that with the Abstract Factory pattern, a class delegates the responsibility of object instantiation to another object via composition whereas the Factory Method pattern uses inheritance and relies on a subclass to handle the desired object instantiation

How it Works

  1. We have JSON file with Location and Name of Factory Class
  2. Using Configuration.java we read the JSON File and read the Configuration
  3. We run TasteFoodFromMenu.java by supplying the JSON file Location as Input
  4. Now by the above method the JAR’s could be built independently and by changing the JSON file we could make changes and deploy the code without restarting the Server

For more detail refer here

Configuration.java

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;

import com.fasterxml.jackson.databind.ObjectMapper;

public class Configuration {

    public static Configuration loadConfiguration(String fileName) throws IOException {    	
    	//Read Name of File
        Path path = FileSystems.getDefault().getPath(fileName);
        
        //Read Contents of File
        String contents = new String(Files.readAllBytes(path), StandardCharsets.UTF_8);
        
        ObjectMapper mapper = new ObjectMapper();
        
        //Map Location and FactoryType
        Configuration config = mapper.readValue(contents, Configuration.class);
        return config;
    }

    private String factoryType;
    private String location;


    public String getLocation() {
        return location;
    }

    public void setLocation(String location) {
        this.location = location;
    }

    public String getFactoryType() {
        return factoryType;
    }

    public void setFactoryType(String factoryType) {
        this.factoryType = factoryType;
    }
}

TasteFoodFromMenu.java

public class TasteFoodFromMenu {
	  public static void main(String[] args) throws IOException, ClassNotFoundException, IllegalAccessException, InstantiationException {
	        Configuration configuration = Configuration.loadConfiguration(args[0]);
	        String location = configuration.getLocation();
	        URL url = new URL(location);
	        URLClassLoader ucl = new URLClassLoader(new URL[]{url});
	        Class<IFoodFactory> cls = (Class<IFoodFactory>) Class.forName(configuration.getFactoryType(), true, ucl);
	        IFoodFactory cameraFactory = cls.newInstance();
	        IFood camera = cameraFactory.prepareFood(); 
	        camera.serveFood();
	    }
}

config.json

{
  "factoryType": "com.mugil.food.ChineseFoodFactory",
  "location": "file:///D:/java/ReadConfFromJSON/lib/FoodMenu.jar"
}