Skip to content

Creational

Creational#

Creational design patterns abstract the object instantiation process. They help in making a system independent of how its objects are created, composed, and represented. They provide flexibility in deciding which objects need to be created for a given use case.

The Factory Method design pattern is a creational pattern that provides an interface for creating objects in a superclass but allows subclasses to alter the type of objects that will be created. This pattern defines a method, which should be implemented by subclasses to create objects. It encapsulates the instantiation logic, allowing the client code to create objects by calling the factory method without needing to know the class of the objects that will be created. This promotes flexibility and scalability as it makes it easier to introduce new types of products without modifying the existing client code, and it promotes loose coupling by separating the creation logic from the business logic.

Key Concepts#

  • Product: The interface or abstract class that defines the type of object that the factory method will create.
  • Concrete Product: The specific implementation of the product interface.
  • Creator: The abstract class or interface that declares the factory method.
  • Concrete Creator: The class that implements the factory method to create a specific product.

Benefits#

  • It promotes loose coupling by eliminating the need to bind application-specific classes into your code.
  • It provides a way to encapsulate the instantiation logic of objects.

The following diagram illustrates the Factory Method pattern with our guitar example, showing how GuitarFactory subclasses create different guitar types while the client remains decoupled from concrete implementations:

classDiagram
    class GuitarFactory {
        <<abstract>>
        +createGuitar() Guitar
    }
    class ElectricGuitarFactory {
        +createGuitar() Guitar
    }
    class AcousticGuitarFactory {
        +createGuitar() Guitar
    }
    class Guitar {
        <<interface>>
        +play()
    }
    class ElectricGuitar {
        +play()
    }
    class AcousticGuitar {
        +play()
    }

    GuitarFactory <|-- ElectricGuitarFactory
    GuitarFactory <|-- AcousticGuitarFactory
    Guitar <|.. ElectricGuitar
    Guitar <|.. AcousticGuitar
    GuitarFactory ..> Guitar
    ElectricGuitarFactory ..> ElectricGuitar : creates
    AcousticGuitarFactory ..> AcousticGuitar : creates

Now let's see how this pattern comes to life across different programming languages with a practical guitar factory example.

Example#

Let's consider a simple example of a guitar factory that creates different types of guitars (Electric and Acoustic).

// The Product interface
interface Guitar {
    void play();
}

// The Concrete Products
class ElectricGuitar implements Guitar {
    @Override
    public void play() {
        System.out.println("Playing an Electric Guitar");
    }
}

class AcousticGuitar implements Guitar {
    @Override
    public void play() {
        System.out.println("Playing an Acoustic Guitar");
    }
}

// The Creator
abstract class GuitarFactory {
    abstract Guitar createGuitar();
}

// The Concrete Creators
class ElectricGuitarFactory extends GuitarFactory {
    @Override
    Guitar createGuitar() {
        return new ElectricGuitar();
    }
}

class AcousticGuitarFactory extends GuitarFactory {
    @Override
    Guitar createGuitar() {
        return new AcousticGuitar();
    }
}

// Client code
public class Main {
    public static void main(String[] args) {
        GuitarFactory guitarFactory = new ElectricGuitarFactory();
        Guitar guitar = guitarFactory.createGuitar();
        guitar.play(); // Output: Playing an Electric Guitar.

        guitarFactory = new AcousticGuitarFactory();
        guitar = guitarFactory.createGuitar();
        guitar.play(); // Output: Playing an Acoustic Guitar.
    }
}
// The Product interface
interface Guitar {
    fun play()
}

// The Concrete Products
class ElectricGuitar: Guitar {
    override fun play() {
        println("Playing an Electric Guitar")
    }
}

class AcousticGuitar: Guitar {
    override fun play() {
        println("Playing an Acoustic Guitar")
    }
}

// The Creator
abstract class GuitarFactory {
    abstract fun createGuitar(): Guitar
}

// The Concrete Creators
class ElectricGuitarFactory: GuitarFactory() {
    override fun createGuitar(): Guitar {
        return ElectricGuitar()
    }
}

class AcousticGuitarFactory: GuitarFactory() {
    override fun createGuitar(): Guitar {
        return AcousticGuitar()
    }
}

// Client code
fun main() {
    var guitarFactory: GuitarFactory = ElectricGuitarFactory()
    var guitar: Guitar = guitarFactory.createGuitar()
    guitar.play() // Output: Playing an Electric Guitar.

    guitarFactory = AcousticGuitarFactory()
    guitar = guitarFactory.createGuitar()
    guitar.play() // Output: Playing an Acoustic Guitar.
}
// The Product interface
interface Guitar {
    play(): void;
}

// The Concrete Products
class ElectricGuitar implements Guitar {
    play(): void {
        console.log("Playing an Electric Guitar");
    }
}

class AcousticGuitar implements Guitar {
    play(): void {
        console.log("Playing an Acoustic Guitar");
    }
}

// The Creator
abstract class GuitarFactory {
    abstract createGuitar(): Guitar;
}

// The Concrete Creators
class ElectricGuitarFactory extends GuitarFactory {
    createGuitar(): Guitar {
        return new ElectricGuitar();
    }
}

class AcousticGuitarFactory extends GuitarFactory {
    createGuitar(): Guitar {
        return new AcousticGuitar();
    }
}

// Client code
let guitarFactory: GuitarFactory = new ElectricGuitarFactory();
let guitar: Guitar = guitarFactory.createGuitar();
guitar.play(); // Output: Playing an Electric Guitar.

guitarFactory = new AcousticGuitarFactory();
guitar = guitarFactory.createGuitar();
guitar.play(); // Output: Playing an Acoustic Guitar.
// The Product interface
abstract class Guitar {
    void play();
}

// The Concrete Products
class ElectricGuitar implements Guitar {
    @override
    void play() {
        print("Playing an Electric Guitar");
    }
}

class AcousticGuitar implements Guitar {
    @override
    void play() {
        print("Playing an Acoustic Guitar");
    }
}

// The Creator
abstract class GuitarFactory {
    Guitar createGuitar();
}

// The Concrete Creators
class ElectricGuitarFactory extends GuitarFactory {
    @override
    Guitar createGuitar() {
        return ElectricGuitar();
    }
}

class AcousticGuitarFactory extends GuitarFactory {
    @override
    Guitar createGuitar() {
        return AcousticGuitar();
    }
}

// Client code
void main() {
    GuitarFactory guitarFactory = ElectricGuitarFactory();
    Guitar guitar = guitarFactory.createGuitar();
    guitar.play(); // Output: Playing an Electric Guitar.

    guitarFactory = AcousticGuitarFactory();
    guitar = guitarFactory.createGuitar();
    guitar.play(); // Output: Playing an Acoustic Guitar.
}
// The Product protocol
protocol Guitar {
    func play()
}

// The Concrete Products
class ElectricGuitar: Guitar {
    func play() {
        print("Playing an Electric Guitar")
    }
}

class AcousticGuitar: Guitar {
    func play() {
        print("Playing an Acoustic Guitar")
    }
}

// The Creator
protocol GuitarFactory {
    func createGuitar() -> Guitar
}

// The Concrete Creators
class ElectricGuitarFactory: GuitarFactory {
    func createGuitar() -> Guitar {
        return ElectricGuitar()
    }
}

class AcousticGuitarFactory: GuitarFactory {
    func createGuitar() -> Guitar {
        return AcousticGuitar()
    }
}

// Client code
var guitarFactory: GuitarFactory = ElectricGuitarFactory()
var guitar: Guitar = guitarFactory.createGuitar()
guitar.play() // Output: Playing an Electric Guitar.

guitarFactory = AcousticGuitarFactory()
guitar = guitarFactory.createGuitar()
guitar.play() // Output: Playing an Acoustic Guitar.
from abc import ABC, abstractmethod

# The Product interface
class Guitar(ABC):
    @abstractmethod
    def play(self):
        pass

# The Concrete Products
class ElectricGuitar(Guitar):
    def play(self):
        print("Playing an Electric Guitar")

class AcousticGuitar(Guitar):
    def play(self):
        print("Playing an Acoustic Guitar")

# The Creator
class GuitarFactory(ABC):
    @abstractmethod
    def create_guitar(self):
        pass

# The Concrete Creators
class ElectricGuitarFactory(GuitarFactory):
    def create_guitar(self):
        return ElectricGuitar()

class AcousticGuitarFactory(GuitarFactory):
    def create_guitar(self):
        return AcousticGuitar()

# Client code
guitar_factory = ElectricGuitarFactory()
guitar = guitar_factory.create_guitar()
guitar.play()  # Output: Playing an Electric Guitar.

guitar_factory = AcousticGuitarFactory()
guitar = guitar_factory.create_guitar()
guitar.play()  # Output: Playing an Acoustic Guitar.

Summary#

The Factory Method design pattern allows for the creation of objects without specifying the exact class of the object that will be created. This promotes flexibility and scalability in your code, making it easier to introduce new types of products without modifying existing code.

The Abstract Factory is a creational design pattern that provides an interface for creating families of related or dependent objects without specifying their concrete classes. This pattern is particularly useful when the system needs to be independent of how its objects are created, composed, and represented.

Key Concepts#

  • Abstract Factory: An interface that declares methods for creating abstract products.
  • Concrete Factory: A class that implements the abstract factory interface to create concrete products.
  • Abstract Product: An interface or abstract class that defines the type of object the factory will create.
  • Concrete Product: The specific implementation of the abstract product interface.

Benefits#

  • Promotes consistency among products in a family.
  • Provides a way to encapsulate the creation logic of related objects.
  • Makes it easier to introduce new products without modifying existing code.

The following diagram shows the Abstract Factory pattern with our guitar and amplifier example, illustrating how each factory creates a complete family of related products (guitar + matching amp):

classDiagram
    class GuitarFactory {
        <<interface>>
        +createGuitar() Guitar
        +createGuitarAmp() GuitarAmp
    }
    class ElectricGuitarFactory {
        +createGuitar() Guitar
        +createGuitarAmp() GuitarAmp
    }
    class AcousticGuitarFactory {
        +createGuitar() Guitar
        +createGuitarAmp() GuitarAmp
    }
    class Guitar {
        <<interface>>
        +play()
    }
    class GuitarAmp {
        <<interface>>
        +amplify()
    }
    class ElectricGuitar {
        +play()
    }
    class AcousticGuitar {
        +play()
    }
    class FenderTwinReverb {
        +amplify()
    }
    class FishmanLoudbox {
        +amplify()
    }

    GuitarFactory <|.. ElectricGuitarFactory
    GuitarFactory <|.. AcousticGuitarFactory
    Guitar <|.. ElectricGuitar
    Guitar <|.. AcousticGuitar
    GuitarAmp <|.. FenderTwinReverb
    GuitarAmp <|.. FishmanLoudbox
    ElectricGuitarFactory ..> ElectricGuitar : creates
    ElectricGuitarFactory ..> FenderTwinReverb : creates
    AcousticGuitarFactory ..> AcousticGuitar : creates
    AcousticGuitarFactory ..> FishmanLoudbox : creates

Notice how each concrete factory creates a complete family of related products - ElectricGuitarFactory pairs electric guitars with electric amps, while AcousticGuitarFactory pairs acoustic guitars with acoustic amps. Let's explore this across multiple languages.

Example#

Let's consider the following example where we have different types of guitars and their amplifiers. We will create an abstract factory for guitars and their amplifiers.

// The Abstract Product for Guitar
interface Guitar {
    void play();
}

// The Concrete Products for Guitar
class ElectricGuitar implements Guitar {
    @Override
    public void play() {
        System.out.println("Playing an Electric Guitar");
    }
}

class AcousticGuitar implements Guitar {
    @Override
    public void play() {
        System.out.println("Playing an Acoustic Guitar");
    }
}

// The Abstract Product for Guitar Amp
interface GuitarAmp {
    void amplify();
}

// The Concrete Products for Guitar Amp
class FenderTwinReverb implements GuitarAmp {
    @Override
    public void amplify() {
        System.out.println("Amplifying with a Fender Twin Reverb");
    }
}

class FishmanLoudbox implements GuitarAmp {
    @Override
    public void amplify() {
        System.out.println("Amplifying with a Fishman Loudbox");
    }
}

// The Abstract Factory
interface GuitarFactory {
    Guitar createGuitar();
    GuitarAmp createGuitarAmp();
}

// The Concrete Factory for Electric Guitar
class ElectricGuitarFactory implements GuitarFactory {
    @Override
    public Guitar createGuitar() {
        return new ElectricGuitar();
    }

    @Override
    public GuitarAmp createGuitarAmp() {
        return new FenderTwinReverb();
    }
}

// The Concrete Factory for Acoustic Guitar
class AcousticGuitarFactory implements GuitarFactory {
    @Override
    public Guitar createGuitar() {
        return new AcousticGuitar();
    }

    @Override
    public GuitarAmp createGuitarAmp() {
        return new FishmanLoudbox();
    }
}

// Client code
public class Main {
    public static void main(String[] args) {
        GuitarFactory electricGuitarFactory = new ElectricGuitarFactory();
        Guitar electricGuitar = electricGuitarFactory.createGuitar();
        GuitarAmp electricAmp = electricGuitarFactory.createGuitarAmp();
        electricGuitar.play();
        electricAmp.amplify(); // Output: Amplifying with a Fender Twin Reverb

        GuitarFactory acousticGuitarFactory = new AcousticGuitarFactory();
        Guitar acousticGuitar = acousticGuitarFactory.createGuitar();
        GuitarAmp acousticAmp = acousticGuitarFactory.createGuitarAmp();
        acousticGuitar.play();
        acousticAmp.amplify(); // Output: Amplifying with a Fishman Loudbox
    }
}
// The Abstract Product for Guitar
interface Guitar {
    fun play()
}

// The Concrete Products for Guitar
class ElectricGuitar: Guitar {
    override fun play() {
        println("Playing an Electric Guitar")
    }
}

class AcousticGuitar: Guitar {
    override fun play() {
        println("Playing an Acoustic Guitar")
    }
}

// The Abstract Product for Guitar Amp
interface GuitarAmp {
    fun amplify()
}

// The Concrete Products for Guitar Amp
class FenderTwinReverb: GuitarAmp {
    override fun amplify() {
        println("Amplifying with a Fender Twin Reverb")
    }
}

class FishmanLoudbox: GuitarAmp {
    override fun amplify() {
        println("Amplifying with a Fishman Loudbox")
    }
}

// The Abstract Factory
interface GuitarFactory {
    fun createGuitar(): Guitar
    fun createGuitarAmp(): GuitarAmp
}

// The Concrete Factory for Electric Guitar
class ElectricGuitarFactory: GuitarFactory {
    override fun createGuitar(): Guitar {
        return ElectricGuitar()
    }

    override fun createGuitarAmp(): GuitarAmp {
        return FenderTwinReverb()
    }
}

// The Concrete Factory for Acoustic Guitar
class AcousticGuitarFactory: GuitarFactory {
    override fun createGuitar(): Guitar {
        return AcousticGuitar()
    }

    override fun createGuitarAmp(): GuitarAmp {
        return FishmanLoudbox()
    }
}

// Client code
fun main() {
    val electricGuitarFactory: GuitarFactory = ElectricGuitarFactory()
    val electricGuitar: Guitar = electricGuitarFactory.createGuitar()
    val electricAmp: GuitarAmp = electricGuitarFactory.createGuitarAmp()
    electricGuitar.play()
    electricAmp.amplify() // Output: Amplifying with a Fender Twin Reverb

    val acousticGuitarFactory: GuitarFactory = AcousticGuitarFactory()
    val acousticGuitar: Guitar = acousticGuitarFactory.createGuitar()
    val acousticAmp: GuitarAmp = acousticGuitarFactory.createGuitarAmp()
    acousticGuitar.play()
    acousticAmp.amplify() // Output: Amplifying with a Fishman Loudbox
}
// The Abstract Product for Guitar
interface Guitar {
    play(): void;
}

// The Concrete Products for Guitar
class ElectricGuitar implements Guitar {
    play(): void {
        console.log("Playing an Electric Guitar");
    }
}

class AcousticGuitar implements Guitar {
    play(): void {
        console.log("Playing an Acoustic Guitar");
    }
}

// The Abstract Product for Guitar Amp
interface GuitarAmp {
    amplify(): void;
}

// The Concrete Products for Guitar Amp
class FenderTwinReverb implements GuitarAmp {
    amplify(): void {
        console.log("Amplifying with a Fender Twin Reverb");
    }
}

class FishmanLoudbox implements GuitarAmp {
    amplify(): void {
        console.log("Amplifying with a Fishman Loudbox");
    }
}

// The Abstract Factory
interface GuitarFactory {
    createGuitar(): Guitar;
    createGuitarAmp(): GuitarAmp;
}

// The Concrete Factory for Electric Guitar
class ElectricGuitarFactory implements GuitarFactory {
    createGuitar(): Guitar {
        return new ElectricGuitar();
    }

    createGuitarAmp(): GuitarAmp {
        return new FenderTwinReverb();
    }
}

// The Concrete Factory for Acoustic Guitar
class AcousticGuitarFactory implements GuitarFactory {
    createGuitar(): Guitar {
        return new AcousticGuitar();
    }

    createGuitarAmp(): GuitarAmp {
        return new FishmanLoudbox();
    }
}

// Client code
const electricGuitarFactory: GuitarFactory = new ElectricGuitarFactory()
const electricGuitar: Guitar = electricGuitarFactory.createGuitar()
const electricAmp: GuitarAmp = electricGuitarFactory.createGuitarAmp()
electricGuitar.play()
electricAmp.amplify() // Output: Amplifying with a Fender Twin Reverb

const acousticGuitarFactory: GuitarFactory = new AcousticGuitarFactory()
const acousticGuitar: Guitar = acousticGuitarFactory.createGuitar()
const acousticAmp: GuitarAmp = acousticGuitarFactory.createGuitarAmp()
acousticGuitar.play()
acousticAmp.amplify() // Output: Amplifying with a Fishman Loudbox
// The Abstract Product for Guitar
abstract class Guitar {
    void play();
}

// The Concrete Products for Guitar
class ElectricGuitar implements Guitar {
    @override
    void play() {
        print("Playing an Electric Guitar");
    }
}

class AcousticGuitar implements Guitar {
    @override
    void play() {
        print("Playing an Acoustic Guitar");
    }
}

// The Abstract Product for Guitar Amp
abstract class GuitarAmp {
    void amplify();
}

// The Concrete Products for Guitar Amp
class FenderTwinReverb implements GuitarAmp {
    @override
    void amplify() {
        print("Amplifying with a Fender Twin Reverb");
    }
}

class FishmanLoudbox implements GuitarAmp {
    @override
    void amplify() {
        print("Amplifying with a Fishman Loudbox");
    }
}

// The Abstract Factory
abstract class GuitarFactory {
    Guitar createGuitar();
    GuitarAmp createGuitarAmp();
}

// The Concrete Factory for Electric Guitar
class ElectricGuitarFactory extends GuitarFactory {
    @override
    Guitar createGuitar() {
        return ElectricGuitar();
    }

    @override
    GuitarAmp createGuitarAmp() {
        return FenderTwinReverb();
    }
}

// The Concrete Factory for Acoustic Guitar
class AcousticGuitarFactory extends GuitarFactory {
    @override
    Guitar createGuitar() {
        return AcousticGuitar();
    }

    @override
    GuitarAmp createGuitarAmp() {
        return FishmanLoudbox();
    }
}

// Client code
void main() {
    ElectricGuitarFactory electricGuitarFactory = ElectricGuitarFactory();
    Guitar electricGuitar = electricGuitarFactory.createGuitar();
    GuitarAmp electricAmp = electricGuitarFactory.createGuitarAmp();
    electricGuitar.play();
    electricAmp.amplify(); // Output: Amplifying with a Fender Twin Reverb

    AcousticGuitarFactory acousticGuitarFactory = AcousticGuitarFactory();
    Guitar acousticGuitar = acousticGuitarFactory.createGuitar();
    GuitarAmp acousticAmp = acousticGuitarFactory.createGuitarAmp();
    acousticGuitar.play();
    acousticAmp.amplify(); // Output: Amplifying with a Fishman Loudbox
}
// The Abstract Product for Guitar
protocol Guitar {
    func play()
}

// The Concrete Products for Guitar
class ElectricGuitar: Guitar {
    func play() {
        print("Playing an Electric Guitar")
    }
}

class AcousticGuitar: Guitar {
    func play() {
        print("Playing an Acoustic Guitar")
    }
}

// The Abstract Product for Guitar Amp
protocol GuitarAmp {
    func amplify()
}

// The Concrete Products for Guitar Amp
class FenderTwinReverb: GuitarAmp {
    func amplify() {
        print("Amplifying with a Fender Twin Reverb")
    }
}

class FishmanLoudbox: GuitarAmp {
    func amplify() {
        print("Amplifying with a Fishman Loudbox")
    }
}

// The Abstract Factory
protocol GuitarFactory {
    func createGuitar() -> Guitar
    func createGuitarAmp() -> GuitarAmp
}

// The Concrete Factory for Electric Guitar
class ElectricGuitarFactory: GuitarFactory {
    func createGuitar() -> Guitar {
        return ElectricGuitar()
    }

    func createGuitarAmp() -> GuitarAmp {
        return FenderTwinReverb()
    }
}

// The Concrete Factory for Acoustic Guitar
class AcousticGuitarFactory: GuitarFactory {
    func createGuitar() -> Guitar {
        return AcousticGuitar()
    }

    func createGuitarAmp() -> GuitarAmp {
        return FishmanLoudbox()
    }
}

// Client code
let electricGuitarFactory: GuitarFactory = ElectricGuitarFactory()
let electricGuitar: Guitar = electricGuitarFactory.createGuitar()
let electricAmp: GuitarAmp = electricGuitarFactory.createGuitarAmp()
electricGuitar.play()
electricAmp.amplify() // Output: Amplifying with a Fender Twin Reverb

let acousticGuitarFactory: GuitarFactory = AcousticGuitarFactory()
let acousticGuitar: Guitar = acousticGuitarFactory.createGuitar()
let acousticAmp: GuitarAmp = acousticGuitarFactory.createGuitarAmp()
acousticGuitar.play()
acousticAmp.amplify() // Output: Amplifying with a Fishman Loudbox
from abc import ABC, abstractmethod

# The Abstract Product for Guitar
class Guitar(ABC):
    @abstractmethod
    def play(self):
        pass

# The Concrete Products for Guitar
class ElectricGuitar(Guitar):
    def play(self):
        print("Playing an Electric Guitar")

class AcousticGuitar(Guitar):
    def play(self):
        print("Playing an Acoustic Guitar")

# The Abstract Product for Guitar Amp
class GuitarAmp(ABC):
    @abstractmethod
    def amplify(self):
        pass

# The Concrete Products for Guitar Amp
class FenderTwinReverb(GuitarAmp):
    def amplify(self):
        print("Amplifying with a Fender Twin Reverb")

class FishmanLoudbox(GuitarAmp):
    def amplify(self):
        print("Amplifying with a Fishman Loudbox")

# The Abstract Factory
class GuitarFactory(ABC):
    @abstractmethod
    def create_guitar(self):
        pass

    @abstractmethod
    def create_guitar_amp(self):
        pass

# The Concrete Factory for Electric Guitar
class ElectricGuitarFactory(GuitarFactory):
    def create_guitar(self):
        return ElectricGuitar()

    def create_guitar_amp(self):
        return FenderTwinReverb()

# The Concrete Factory for Acoustic Guitar
class AcousticGuitarFactory(GuitarFactory):
    def create_guitar(self):
        return AcousticGuitar()

    def create_guitar_amp(self):
        return FishmanLoudbox()

# Client code
electric_guitar_factory = ElectricGuitarFactory()
electric_guitar = electric_guitar_factory.create_guitar()
electric_amp = electric_guitar_factory.create_guitar_amp()
electric_guitar.play()
electric_amp.amplify()  # Output: Amplifying with a Fender Twin Reverb

acoustic_guitar_factory = AcousticGuitarFactory()
acoustic_guitar = acoustic_guitar_factory.create_guitar()
acoustic_amp = acoustic_guitar_factory.create_guitar_amp()
acoustic_guitar.play()
acoustic_amp.amplify()  # Output: Amplifying with a Fishman Loudbox

Summary#

The Abstract Factory design pattern allows for the creation of families of related or dependent objects without specifying their concrete classes. This promotes flexibility and scalability in your code, making it easier to introduce new products without modifying existing code.

The Builder pattern is a creational design pattern that allows for the step-by-step construction of complex objects. It separates the construction of a complex object from its representation, enabling the same construction process to create different representations.

Key Concepts#

  • Builder: An interface or abstract class that defines the methods for creating parts of the product.
  • Concrete Builder: A class that implements the builder interface to construct and assemble the parts of the product.
  • Director: A class that constructs an object using the builder interface. Product: The complex object that is being built.

Benefits#

  • Provides a clear separation between the construction and representation of an object.
  • Allows for the creation of different representations of an object using the same construction process.
  • Makes it easier to create complex objects with many optional parameters.

The following diagram demonstrates the Builder pattern with our guitar example, showing how GuitarBuilder provides a fluent interface to construct Guitar objects step-by-step:

classDiagram
    class Guitar {
        -brand: String
        -model: String
        -type: String
        +toString() String
    }
    class GuitarBuilder {
        -brand: String
        -model: String
        -type: String
        +setBrand(String) GuitarBuilder
        +setModel(String) GuitarBuilder
        +setType(String) GuitarBuilder
        +build() Guitar
    }

    GuitarBuilder ..> Guitar : builds

The builder encapsulates the construction logic and provides a fluent API for assembling Guitar objects with various attributes. Let's see this pattern in action across different languages.

Example#

Let's consider a guitar-related example where we want to build different types of guitars with various attributes (like brand, model, and type).

// The Product
class Guitar {
    private String brand;
    private String model;
    private String type;

    public Guitar(String brand, String model, String type) {
        this.brand = brand;
        this.model = model;
        this.type = type;
    }

    @Override
    public String toString() {
        return "Guitar [Brand: " + brand + ", Model: " + model + ", Type: " + type + "]";
    }
}

// The Builder
class GuitarBuilder {
    private String brand;
    private String model;
    private String type;

    public GuitarBuilder setBrand(String brand) {
        this.brand = brand;
        return this;
    }

    public GuitarBuilder setModel(String model) {
        this.model = model;
        return this;
    }

    public GuitarBuilder setType(String type) {
        this.type = type;
        return this;
    }

    public Guitar build() {
        return new Guitar(brand, model, type);
    }
}

// Client code
public class Main {
    public static void main(String[] args) {
        Guitar guitar = new GuitarBuilder()
                .setBrand("Fender")
                .setModel("Stratocaster")
                .setType("Electric")
                .build();
        System.out.println(guitar); // Output: Guitar [Brand: Fender, Model: Stratocaster, Type: Electric]
    }
}
// The Product
data class Guitar(val brand: String, val model: String, val type: String)

// The Builder
class GuitarBuilder {
    private var brand: String = ""
    private var model: String = ""
    private var type: String = ""

    fun setBrand(brand: String) = apply { this.brand = brand }
    fun setModel(model: String) = apply { this.model = model }
    fun setType(type: String) = apply { this.type = type }

    fun build(): Guitar {
        return Guitar(brand, model, type)
    }
}

// Client code
fun main() {
    val guitar = GuitarBuilder()
        .setBrand("Fender")
        .setModel("Stratocaster")
        .setType("Electric")
        .build()
    println(guitar) // Output: Guitar(brand=Fender, model=Stratocaster, type=Electric)
}
// The Product
class Guitar {
    constructor(public brand: string, public model: string, public type: string) {}

    toString(): string {
        return `Guitar [Brand: ${this.brand}, Model: ${this.model}, Type: ${this.type}]`;
    }
}

// The Builder
class GuitarBuilder {
    private brand: string = '';
    private model: string = '';
    private type: string = '';

    setBrand(brand: string): this {
        this.brand = brand;
        return this;
    }

    setModel(model: string): this {
        this.model = model;
        return this;
    }

    setType(type: string): this {
        this.type = type;
        return this;
    }

    build(): Guitar {
        return new Guitar(this.brand, this.model, this.type);
    }
}

// Client code
const guitar = new GuitarBuilder()
    .setBrand("Fender")
    .setModel("Stratocaster")
    .setType("Electric")
    .build();
console.log(guitar.toString()); // Output: Guitar [Brand: Fender, Model: Stratocaster, Type: Electric]
// The Product
class Guitar {
    String brand;
    String model;
    String type;

    Guitar(this.brand, this.model, this.type);

    @override
    String toString() {
        return 'Guitar [Brand: $brand, Model: $model, Type: $type]';
    }
}

// The Builder
class GuitarBuilder {
    String _brand = '';
    String _model = '';
    String _type = '';

    GuitarBuilder setBrand(String brand) {
        _brand = brand;
        return this;
    }

    GuitarBuilder setModel(String model) {
        _model = model;
        return this;
    }

    GuitarBuilder setType(String type) {
        _type = type;
        return this;
    }

    Guitar build() {
        return Guitar(_brand, _model, _type);
    }
}

// Client code
void main() {
    Guitar guitar = GuitarBuilder()
        .setBrand("Fender")
        .setModel("Stratocaster")
        .setType("Electric")
        .build();
    print(guitar); // Output: Guitar [Brand: Fender, Model: Stratocaster, Type: Electric]
}
// The Product
class Guitar {
    var brand: String
    var model: String
    var type: String

    init(brand: String, model: String, type: String) {
        self.brand = brand
        self.model = model
        self.type = type
    }

    var description: String {
        return "Guitar [Brand: \(brand), Model: \(model), Type: \(type)]"
    }
}

// The Builder
class GuitarBuilder {
    private var brand: String = ""
    private var model: String = ""
    private var type: String = ""

    func setBrand(_ brand: String) -> GuitarBuilder {
        self.brand = brand
        return self
    }

    func setModel(_ model: String) -> GuitarBuilder {
        self.model = model
        return self
    }

    func setType(_ type: String) -> GuitarBuilder {
        self.type = type
        return self
    }

    func build() -> Guitar {
        return Guitar(brand: brand, model: model, type: type)
    }
}

// Client code
let guitar = GuitarBuilder()
    .setBrand("Fender")
    .setModel("Stratocaster")
    .setType("Electric")
    .build()
print(guitar.description) // Output: Guitar [Brand: Fender, Model: Stratocaster, Type: Electric]
# The Product
class Guitar:
    def __init__(self, brand, model, type):
        self.brand = brand
        self.model = model
        self.type = type

    def __str__(self):
        return f"Guitar [Brand: {self.brand}, Model: {self.model}, Type: {self.type}]"

# The Builder
class GuitarBuilder:
    def __init__(self):
        self.brand = ""
        self.model = ""
        self.type = ""

    def set_brand(self, brand):
        self.brand = brand
        return self

    def set_model(self, model):
        self.model = model
        return self

    def set_type(self, type):
        self.type = type
        return self

    def build(self):
        return Guitar(self.brand, self.model, self.type)

# Client code
guitar = GuitarBuilder()\
    .set_brand("Fender")\
    .set_model("Stratocaster")\
    .set_type("Electric")\
    .build()
print(guitar)  # Output: Guitar [Brand: Fender, Model: Stratocaster, Type: Electric]

Summary#

The Builder design pattern allows for the step-by-step construction of complex objects, providing a clear separation between the construction and representation of an object. This pattern is particularly useful when creating objects with many optional parameters.

The Prototype pattern is a creational design pattern that allows cloning of objects, even complex ones, without coupling to their specific classes. This pattern is particularly useful when the cost of creating a new instance of an object is more expensive than cloning an existing one.

Key Concepts#

  • Prototype: An interface that declares a method for cloning itself.
  • Concrete Prototype: The class that implements the Prototype interface and defines the method for cloning.

Benefits#

  • Reduces the need for subclasses.
  • Provides a way to create objects at runtime.

The following diagram illustrates the Prototype pattern with our guitar example, showing how Guitar objects can clone themselves to create copies without complex reinitialization:

classDiagram
    class GuitarPrototype {
        <<interface>>
        +clone() GuitarPrototype
    }
    class Guitar {
        -brand: String
        -model: String
        +clone() GuitarPrototype
        +toString() String
    }

    GuitarPrototype <|.. Guitar

The beauty of this pattern lies in its ability to create new Guitar objects by copying existing ones, avoiding the overhead of complex initialization. Let's explore this across different languages.

Example#

Let's consider a guitar prototype that can be cloned.

// The Prototype interface
interface GuitarPrototype {
    GuitarPrototype clone();
}

// The Concrete Prototype
class Guitar implements GuitarPrototype {
    private String brand;
    private String model;

    public Guitar(String brand, String model) {
        this.brand = brand;
        this.model = model;
    }

    @Override
    public GuitarPrototype clone() {
        return new Guitar(this.brand, this.model);
    }

    @Override
    public String toString() {
        return "Guitar [Brand: " + brand + ", Model: " + model + "]";
    }
}

// Client code
public class Main {
    public static void main(String[] args) {
        Guitar originalGuitar = new Guitar("Gibson", "Les Paul");
        Guitar clonedGuitar = (Guitar) originalGuitar.clone();
        System.out.println(clonedGuitar); // Output: Guitar [Brand: Gibson, Model: Les Paul]
    }
}
// The Prototype interface
interface GuitarPrototype {
    fun clone(): GuitarPrototype
}

// The Concrete Prototype
class Guitar(private val brand: String, private val model: String) : GuitarPrototype {
    override fun clone(): GuitarPrototype {
        return Guitar(brand, model)
    }

    override fun toString(): String {
        return "Guitar [Brand: $brand, Model: $model]"
    }
}

// Client code
fun main() {
    val originalGuitar = Guitar("Gibson", "Les Paul")
    val clonedGuitar = originalGuitar.clone() as Guitar
    println(clonedGuitar) // Output: Guitar [Brand: Gibson, Model: Les Paul]
}
// The Prototype interface
interface GuitarPrototype {
    clone(): GuitarPrototype;
}

// The Concrete Prototype
class Guitar implements GuitarPrototype {
    constructor(private brand: string, private model: string) {}

    clone(): GuitarPrototype {
        return new Guitar(this.brand, this.model);
    }

    toString(): string {
        return `Guitar [Brand: ${this.brand}, Model: ${this.model}]`;
    }
}

// Client code
const originalGuitar = new Guitar("Gibson", "Les Paul");
const clonedGuitar = originalGuitar.clone();
console.log(clonedGuitar.toString()); // Output: Guitar [Brand: Gibson, Model: Les Paul]
// The Prototype interface
abstract class GuitarPrototype {
    GuitarPrototype clone();
}

// The Concrete Prototype
class Guitar implements GuitarPrototype {
    final String brand;
    final String model;

    Guitar(this.brand, this.model);

    @override
    GuitarPrototype clone() {
        return Guitar(brand, model);
    }

    @override
    String toString() {
        return 'Guitar [Brand: $brand, Model: $model]';
    }
}

// Client code
void main() {
    Guitar originalGuitar = Guitar("Gibson", "Les Paul");
    Guitar clonedGuitar = originalGuitar.clone() as Guitar;
    print(clonedGuitar); // Output: Guitar [Brand: Gibson, Model: Les Paul]
}
// The Prototype protocol
protocol GuitarPrototype {
    func clone() -> GuitarPrototype
}

// The Concrete Prototype
class Guitar: GuitarPrototype {
    var brand: String
    var model: String

    init(brand: String, model: String) {
        self.brand = brand
        self.model = model
    }

    func clone() -> GuitarPrototype {
        return Guitar(brand: brand, model: model)
    }

    var description: String {
        return "Guitar [Brand: \(brand), Model: \(model)]"
    }
}

// Client code
let originalGuitar = Guitar(brand: "Gibson", model: "Les Paul")
let clonedGuitar = originalGuitar.clone() as! Guitar
print(clonedGuitar.description) // Output: Guitar [Brand: Gibson, Model: Les Paul]
# The Prototype interface
class GuitarPrototype:
    def clone(self):
        raise NotImplementedError

# The Concrete Prototype
class Guitar(GuitarPrototype):
    def __init__(self, brand, model):
        self.brand = brand
        self.model = model

    def clone(self):
        return Guitar(self.brand, self.model)

    def __str__(self):
        return f"Guitar [Brand: {self.brand}, Model: {self.model}]"

# Client code
original_guitar = Guitar("Gibson", "Les Paul")
cloned_guitar = original_guitar.clone()
print(cloned_guitar)  # Output: Guitar [Brand: Gibson, Model: Les Paul]

Summary#

The Prototype design pattern allows for the creation of new objects by copying an existing object, which can be more efficient than creating new instances from scratch. This pattern is particularly useful when the cost of instantiation is high or when the objects are complex.

The Singleton pattern is a creational design pattern that ensures a class has only one instance and provides a global point of access to it. This pattern is particularly useful when exactly one object is needed to coordinate actions across the system.

Key Concepts#

  • Singleton: A class that restricts instantiation to a single instance and provides a global access point.

Benefits#

  • Controlled access to a single instance.
  • Reduced memory footprint.

The following diagram depicts the Singleton pattern with our guitar factory example, demonstrating how GuitarFactory restricts instantiation to ensure only one factory instance exists:

classDiagram
    class GuitarFactory {
        -instance: GuitarFactory$
        -GuitarFactory()
        +getInstance()$ GuitarFactory
        +createGuitar(String) Guitar
    }

    note for GuitarFactory "Private constructor prevents direct instantiation. Static getInstance() provides global access point."

The Singleton ensures controlled access to the guitar factory by centralizing instance management, guaranteeing all guitar creation goes through a single factory instance. Let's see how different languages implement this pattern.

Example#

Let's consider a guitar factory that should only have one instance.

// The Singleton class
class GuitarFactory {
    private static GuitarFactory instance;

    private GuitarFactory() {}

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

    public Guitar createGuitar(String type) {
        if (type.equals("Electric")) {
            return new ElectricGuitar();
        } else {
            return new AcousticGuitar();
        }
    }
}

// Client code
public class Main {
    public static void main(String[] args) {
        GuitarFactory factory = GuitarFactory.getInstance();
        Guitar guitar = factory.createGuitar("Electric");
        System.out.println(guitar); // Output: Electric Guitar
    }
}
// The Singleton class
object GuitarFactory {
    fun createGuitar(type: String): Guitar {
        return if (type == "Electric") {
            ElectricGuitar()
        } else {
            AcousticGuitar()
        }
    }
}

// Client code
fun main() {
    val guitar = GuitarFactory.createGuitar("Electric")
    println(guitar) // Output: Electric Guitar
}
// The Singleton class
class GuitarFactory {
    private static instance: GuitarFactory;

    private constructor() {}

    public static getInstance(): GuitarFactory {
        if (!GuitarFactory.instance) {
            GuitarFactory.instance = new GuitarFactory();
        }
        return GuitarFactory.instance;
    }

    public createGuitar(type: string): Guitar {
        if (type === "Electric") {
            return new ElectricGuitar();
        } else {
            return new AcousticGuitar();
        }
    }
}

// Client code
const factory = GuitarFactory.getInstance();
const guitar = factory.createGuitar("Electric");
console.log(guitar); // Output: Electric Guitar
// The Singleton class
class GuitarFactory {
    static final GuitarFactory _instance = GuitarFactory._internal();

    factory GuitarFactory() {
        return _instance;
    }

    GuitarFactory._internal();

    Guitar createGuitar(String type) {
        if (type == "Electric") {
            return ElectricGuitar();
        } else {
            return AcousticGuitar();
        }
    }
}

// Client code
void main() {
    GuitarFactory factory = GuitarFactory();
    Guitar guitar = factory.createGuitar("Electric");
    print(guitar); // Output: Electric Guitar
}
// The Singleton class
class GuitarFactory {
    static let shared = GuitarFactory()

    private init() {}

    func createGuitar(type: String) -> Guitar {
        if type == "Electric" {
            return ElectricGuitar()
        } else {
            return AcousticGuitar()
        }
    }
}

// Client code
let factory = GuitarFactory.shared
let guitar = factory.createGuitar(type: "Electric")
print(guitar) // Output: Electric Guitar
# The Singleton class
class Guitar:
    def __init__(self, type):
        self.type = type

    def __str__(self):
        return f"{self.type} Guitar"

class GuitarFactory:
    _instance = None

    def __new__(cls):
        if cls._instance is None:
            cls._instance = super(GuitarFactory, cls).__new__(cls)
        return cls._instance

    def create_guitar(self, type):
        return Guitar(type)

# Client code
factory = GuitarFactory()
guitar = factory.create_guitar("Electric")
print(guitar)  # Output: Electric Guitar

Summary#

The Singleton design pattern ensures that a class has only one instance and provides a global point of access to it. This pattern is particularly useful when exactly one object is needed to coordinate actions across the system, such as managing shared resources or configurations.