Creational Design Pattern


Factory Method Pattern

The Factory Method pattern is a creational design pattern that provides an interface for creating objects in a superclass, allowing subclasses to alter the type of objects that will be created.

When to Use

  1. When a class doesn't know what subclasses will be required to create
  2. When a class wants its subclasses to specify the objects it creates
  3. When classes delegate responsibility to one of several helper subclasses, and you want to localize the knowledge of which helper subclass is the delegate

Example

interface Animal {
    void speak();
    void action();
}
class Dog implements Animal {
    @Override
    public void speak() {
        System.out.println("Dog barks");
    }

    @Override
    public void action() {
        System.out.println("Dog walks");
    }
}

class Cat implements Animal {
    @Override
    public void speak() {
        System.out.println("Cat meows");
    }

    @Override
    public void action() {
        System.out.println("Cat climbs");
    }
}

abstract class AnimalFactory {
    abstract Animal createAnimal();

    public Animal getAnimal() {
        Animal animal = createAnimal();
        animal.speak();
        animal.action();
        return animal;
    }
}

class DogFactory extends AnimalFactory {
    @Override
    Animal createAnimal() {
        return new Dog();
    }
}

class CatFactory extends AnimalFactory {
    @Override
    Animal createAnimal() {
        return new Cat();
    }
}

public class FactoryMethodExample {
    public static void main(String[] args) {
        AnimalFactory dogFactory = new DogFactory();
        AnimalFactory catFactory = new CatFactory();

        System.out.println("Dog:");
        dogFactory.getAnimal();

        System.out.println("\nCat:");
        catFactory.getAnimal();
    }
}

Builder Creational Design Pattern

The Builder pattern is a creational design pattern that allows for the step-by-step creation of complex objects using the correct sequence of actions. It separates the construction of a complex object from its representation, allowing the same construction process to create various representations.

When to Use

  1. When the algorithm for creating a complex object should be independent of the parts that make up the object
  2. When the construction process must allow different representations for the object being constructed
  3. When you need to create complex objects with many optional components or configurations

Structure

  1. Product: The complex object being built
  2. Builder: An interface that declares the construction steps
  3. Concrete Builder: Implements the Builder interface and provides specific implementations for the construction steps
  4. Director: Controls the building process using the Builder interface
  5. Client: Uses the Director and Builder to construct the Product

Example

class Computer {
    private String CPU;
    private String RAM;
    private String storage;
    private String graphicsCard;

    @Override
    public String toString() {
        return "Computer{" +
                "CPU='" + CPU + '\'' +
                ", RAM='" + RAM + '\'' +
                ", storage='" + storage + '\'' +
                ", graphicsCard='" + graphicsCard + '\'' +
                '}';
    }

    void setCPU(String CPU) { this.CPU = CPU; }
    void setRAM(String RAM) { this.RAM = RAM; }
    void setStorage(String storage) { this.storage = storage; }
    void setGraphicsCard(String graphicsCard) { this.graphicsCard = graphicsCard; }
}

interface ComputerBuilder {
    void buildCPU(String CPU);
    void buildRAM(String RAM);
    void buildStorage(String storage);
    void buildGraphicsCard(String graphicsCard);
    Computer getResult();
}

class GamingComputerBuilder implements ComputerBuilder {
    private Computer computer;

    public GamingComputerBuilder() {
        this.computer = new Computer();
    }

    @Override
    public void buildCPU(String CPU) {
        computer.setCPU(CPU);
    }

    @Override
    public void buildRAM(String RAM) {
        computer.setRAM(RAM);
    }

    @Override
    public void buildStorage(String storage) {
        computer.setStorage(storage);
    }

    @Override
    public void buildGraphicsCard(String graphicsCard) {
        computer.setGraphicsCard(graphicsCard);
    }

    @Override
    public Computer getResult() {
        return computer;
    }
}

class ComputerAssembler {
    private ComputerBuilder computerBuilder;

    public ComputerAssembler(ComputerBuilder computerBuilder) {
        this.computerBuilder = computerBuilder;
    }

    public Computer assembleComputer() {
        computerBuilder.buildCPU("Intel i9");
        computerBuilder.buildRAM("32GB DDR4");
        computerBuilder.buildStorage("1TB NVMe SSD");
        computerBuilder.buildGraphicsCard("NVIDIA RTX 3080");
        return computerBuilder.getResult();
    }
}

public class Main {
    public static void main(String[] args) {
        ComputerBuilder builder = new GamingComputerBuilder();
        ComputerAssembler assembler = new ComputerAssembler(builder);
        
        Computer gamingPC = assembler.assembleComputer();
        System.out.println("Assembled Gaming PC: " + gamingPC);
    }
}

Prototype Design Pattern

The Prototype pattern is used to create new objects by copying an existing object, known as the prototype.

When to Use

  1. When your system should be independent of how its products are created, composed, and represented
  2. When the classes to instantiate are specified at run-time
  3. To avoid building a class hierarchy of factories that parallels the class hierarchy of products

Example

import java.util.HashMap;
import java.util.Map;

interface Prototype {
    Prototype clone();
    void setName(String name);
    String getName();
}

class Animal implements Prototype {
    private String name;
    private String type;

    public Animal(String type) {
        this.type = type;
    }

    @Override
    public Prototype clone() {
        Animal clonedAnimal = new Animal(this.type);
        clonedAnimal.setName(this.name);
        return clonedAnimal;
    }

    @Override
    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String getName() {
        return name;
    }

    @Override
    public String toString() {
        return "Animal [name=" + name + ", type=" + type + "]";
    }
}

class AnimalCache {
    private Map<String, Prototype> animalMap = new HashMap<>();

    public AnimalCache() {
        animalMap.put("dog", new Animal("Dog"));
        animalMap.put("cat", new Animal("Cat"));
    }

    public Prototype getAnimal(String animalType) {
        return animalMap.get(animalType).clone();
    }
}

public class Main {
    public static void main(String[] args) {
        AnimalCache animalCache = new AnimalCache();

        Animal clonedDog = (Animal) animalCache.getAnimal("dog");
        clonedDog.setName("Buddy");
        System.out.println(clonedDog);

        Animal clonedCat = (Animal) animalCache.getAnimal("cat");
        clonedCat.setName("Whiskers");
        System.out.println(clonedCat);

        Animal anotherDog = (Animal) animalCache.getAnimal("dog");
        anotherDog.setName("Max");
        System.out.println(anotherDog);
    }
}