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 helps you create objects without specifying the exact class of object that will be created. Instead, you define a method for creating an object in a base class, and subclasses decide what type of object to make. This keeps the main code independent from the details of which classes are being used. As a result, you can add new types of objects later, just by adding new subclasses, without changing the main code.
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.
Let's make the Factory Method pattern more concrete with a practical example. Imagine we want to model a guitar factory that can create different types of guitars, such as electric and acoustic guitars. By using the Factory Method, we can separate the logic of which specific guitar to create from the rest of our code, promoting flexibility and maintainability.
The diagram below illustrates how the Factory Method pattern is applied in our guitar factory scenario: the GuitarFactory base class declares a factory method, and its subclasses (ElectricGuitarFactory, AcousticGuitarFactory) decide which concrete guitar (ElectricGuitar, AcousticGuitar) to instantiate. The client code interacts only with the abstract interfaces and factories, staying decoupled from the concrete classes:
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 in code by implementing a simple guitar factory that creates electric and acoustic guitars using the Factory Method approach.
// 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 design pattern helps you create families of related or dependent objects without specifying their concrete classes. Instead, you define an interface for creating a set of related products, and concrete factories decide which specific products to make. This keeps the main code independent from the details of which product families are being used. As a result, you can add new families of products later, just by adding new concrete factories, without changing the main code.
Key Concepts
- Abstract Factory: The interface that declares methods for creating each type of abstract product.
- Concrete Factory: The class that implements the abstract factory interface to create a specific family of products.
- Abstract Product: The 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
- It promotes consistency among products in a family by ensuring related objects are used together.
- It provides a way to encapsulate the creation logic of related objects.
Let's make the Abstract Factory pattern more concrete with a practical example. Imagine we want to model a guitar factory that can create not just guitars, but complete setups including matching amplifiers. By using the Abstract Factory, we can ensure that electric guitars are paired with electric amps, and acoustic guitars with acoustic amps, while keeping the client code decoupled from specific product classes.
The diagram below illustrates how the Abstract Factory pattern is applied in our guitar factory scenario: the GuitarFactory interface declares factory methods for both guitars and amplifiers, and its concrete implementations (ElectricGuitarFactory, AcousticGuitarFactory) decide which specific products to instantiate. The client code interacts only with the abstract interfaces and factories, staying decoupled from the concrete classes:
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
Now, let's see how this pattern comes to life in code by implementing a guitar factory that creates complete guitar setups with matching amplifiers using the Abstract Factory approach.
// 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 design pattern helps you construct complex objects step by step. Instead of using a constructor with many parameters, you use a builder that assembles the object piece by piece. This keeps the construction logic separate from the object's representation, allowing the same construction process to create different configurations. As a result, you can create objects with many optional parameters cleanly, without needing multiple constructor overloads.
Key Concepts
- Builder: The interface or abstract class that defines the methods for creating parts of the product.
- Concrete Builder: The class that implements the builder interface to construct and assemble the parts of the product.
- Director: The class that constructs an object using the builder interface.
- Product: The complex object that is being built.
Benefits
- It provides a clear separation between the construction and representation of an object.
- It allows for the creation of different representations of an object using the same construction process.
Let's make the Builder pattern more concrete with a practical example. Imagine we want to build different types of guitars with various attributes like brand, model, and type. By using the Builder, we can construct these guitar objects step by step with a fluent interface, making the code readable and flexible.
The diagram below illustrates how the Builder pattern is applied in our guitar scenario: the GuitarBuilder provides setter methods that return the builder itself, enabling method chaining, and a build() method that creates the final Guitar object:
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
Now, let's see how this pattern comes to life in code by implementing a guitar builder that constructs guitars with various attributes using the Builder approach.
// 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 design pattern helps you create new objects by cloning existing ones, even complex objects, without coupling to their specific classes. Instead of instantiating new objects directly, you copy a prototype that already has the desired configuration. This keeps the client code independent from the concrete classes being cloned. As a result, you can create new objects efficiently, especially when the cost of initialization is higher than the cost of copying.
Key Concepts
- Prototype: The interface that declares a method for cloning itself.
- Concrete Prototype: The class that implements the Prototype interface and defines the method for cloning.
Benefits
- It avoids the cost of creating objects from scratch when initialization is expensive or complex.
- It hides concrete classes from the client, allowing new types to be cloned without changing client code.
Let's make the Prototype pattern more concrete with a practical example. Imagine we have a guitar with a specific configuration that we want to duplicate. By using the Prototype, we can clone the existing guitar object to create new ones with the same attributes, avoiding the overhead of complex initialization.
The diagram below illustrates how the Prototype pattern is applied in our guitar scenario: the GuitarPrototype interface declares a clone() method, and the Guitar class implements it to create a copy of itself:
classDiagram
class GuitarPrototype {
<<interface>>
+clone() GuitarPrototype
}
class Guitar {
-brand: String
-model: String
+clone() GuitarPrototype
+toString() String
}
GuitarPrototype <|.. Guitar
Now, let's see how this pattern comes to life in code by implementing a guitar prototype that can be cloned to create new guitar objects.
// 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 design pattern helps you ensure a class has only one instance while providing a global point of access to it. Instead of allowing multiple instances to be created, you restrict instantiation through a private constructor and expose a static method to retrieve the single instance. This keeps instance management centralized and controlled. As a result, you can coordinate actions across the system through a single shared object, avoiding conflicts from multiple instances.
Key Concepts
- Singleton: The class that restricts instantiation to a single instance and provides a global access point.
Benefits
- It provides controlled access to a single instance throughout the application.
- It reduces memory footprint by preventing duplicate instances.
Let's make the Singleton pattern more concrete with a practical example. Imagine we have a guitar factory that should only exist as one instance throughout the application. By using the Singleton, we ensure that all guitar creation requests go through the same factory instance, maintaining consistency and avoiding resource duplication.
The diagram below illustrates how the Singleton pattern is applied in our guitar factory scenario: the GuitarFactory has a private constructor to prevent direct instantiation and a static getInstance() method that returns the single instance:
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."
Now, let's see how this pattern comes to life in code by implementing a guitar factory that ensures only one instance exists throughout the application.
// 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
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.