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.
- Abstracts object creation mechanism from consumers
- Useful when the subclass of an object instantiated can vary
- Provides flexibility in creating objects of unknown types or numbers
When to Use
- When a class doesn't know what subclasses will be required to create
- When a class wants its subclasses to specify the objects it creates
- 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.
- Separates object construction from its representation
- Allows for step-by-step creation of complex objects
- Provides better control over the construction process
- Enables the creation of different representations using the same construction process
When to Use
- When the algorithm for creating a complex object should be independent of the parts that make up the object
- When the construction process must allow different representations for the object being constructed
- When you need to create complex objects with many optional components or configurations
Structure
- Product: The complex object being built
- Builder: An interface that declares the construction steps
- Concrete Builder: Implements the Builder interface and provides specific implementations for the construction steps
- Director: Controls the building process using the Builder interface
- 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.
- Useful when creating several instances of a prototypical object
- Allows cloning of objects without coupling to their specific classes
- Often used in situations where the creation of a new object is more efficient through copying
When to Use
- When your system should be independent of how its products are created, composed, and represented
- When the classes to instantiate are specified at run-time
- 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);
}
}