DEV Community

Cover image for Design patterns: Part One - A brief explanation of creational pattern
andres paladines
andres paladines

Posted on • Edited on

Design patterns: Part One - A brief explanation of creational pattern

The picture was taken from boredpanda.

Hello everyone!

Starting my first post with one of the most basic but never unnecessary topics; Design patterns.

Let's start listing them:

.
├── Creational Patterns
│   ├── Singleton
│   ├── Factory Method
│   ├── Builder
│   ├── Abstract Factory
│   ├── Prototype
├── Structural Patterns
│   ├── Adapter
│   ├── Decorator
│   ├── Facade
│   ├── Composite
│   ├── Proxy
├── Behavioral Patterns
│   ├── Observer
│   ├── Strategy
│   ├── Command
│   ├── Chain of Responsibility
│   ├── Mediator
│   ├── State
│   ├── Template Method
.
Enter fullscreen mode Exit fullscreen mode

Creational Patterns

This post will focus on Creational Patterns and how to construct them with swift and javascript. So, Let's start!

Singleton

Ensures a class has only one instance and provides a global point of access to it.
Use case: A central configuration manager for app settings.

Swift:

class ConfigurationManager {
    static let shared = ConfigurationManager()

    private init() { }

    var settings: [String: Any] = [:]

    func updateSetting(key: String, value: Any) {
        settings[key] = value
    }

    func getSetting(key: String) -> Any? {
        return settings[key]
    }
}

// Usage
ConfigurationManager.shared.updateSetting(key: "theme", value: "dark")
let theme = ConfigurationManager.shared.getSetting(key: "theme")
Enter fullscreen mode Exit fullscreen mode

JavaScript:

class ConfigurationManager {
    constructor() {
        if (ConfigurationManager.instance == null) {
            this.settings = {};
            ConfigurationManager.instance = this;
        }
        return ConfigurationManager.instance;
    }

    updateSetting(key, value) {
        this.settings[key] = value;
    }

    getSetting(key) {
        return this.settings[key];
    }
}

const instance = new ConfigurationManager();
Object.freeze(instance);

// Usage
instance.updateSetting("theme", "dark");
const theme = instance.getSetting("theme");

Enter fullscreen mode Exit fullscreen mode

Factory Method

Defines an interface for creating different objects from a superclass or protocol from another object.

Use Case: Handling different types of payment gateways (e.g., PayPal, Stripe, Apple Pay).

Swift:

enum PaymentProvider {
    case paypal
    case stripe
}

protocol PaymentGateway {
    func processPayment(amount: Double)
}

class PayPal: PaymentGateway {
    func processPayment(amount: Double) {
        print("Processing payment through PayPal: \(amount)")
    }
}

class Stripe: PaymentGateway {
    func processPayment(amount: Double) {
        print("Processing payment through Stripe: \(amount)")
    }
}

class PaymentGatewayFactory {
    static func createPaymentGateway(type: PaymentProvider) -> PaymentGateway? {
        switch type {
        case .paypal:
            return PayPal()
        case .stripe:
            return Stripe()
        default:
            return nil
        }
    }
}

// Usage
let gateway = PaymentGatewayFactory.createPaymentGateway(type: "PayPal")
gateway?.processPayment(amount: 100.0)
Enter fullscreen mode Exit fullscreen mode

JavaScript:

class PayPal {
    processPayment(amount) {
        console.log(`Processing payment through PayPal: ${amount}`);
    }
}

class Stripe {
    processPayment(amount) {
        console.log(`Processing payment through Stripe: ${amount}`);
    }
}

class PaymentGatewayFactory {
    static createPaymentGateway(type) {
        switch (type) {
            case "PayPal":
                return new PayPal();
            case "Stripe":
                return new Stripe();
            default:
                return null;
        }
    }
}

// Usage
const gateway = PaymentGatewayFactory.createPaymentGateway("PayPal");
gateway.processPayment(100.0);
Enter fullscreen mode Exit fullscreen mode

Builder

Separates the whole construction process of a complex object, allowing to create different representations of it.
Use Case: Creating complex UI components such as custom dialog boxes.

Swift

class Dialog {
    var title: String = ""
    var message: String = ""
    var buttons: [String] = []

    func show() {
        print("Title: \(title)")
        print("Message: \(message)")
        print("Buttons: \(buttons.joined(separator: ", "))")
    }
}

class DialogBuilder {
    private var dialog = Dialog()

    func setTitle(_ title: String) -> DialogBuilder {
        dialog.title = title
        return self
    }

    func setMessage(_ message: String) -> DialogBuilder {
        dialog.message = message
        return self
    }

    func addButton(_ button: String) -> DialogBuilder {
        dialog.buttons.append(button)
        return self
    }

    func build() -> Dialog {
        return dialog
    }
}

// Usage
let dialog = DialogBuilder()
    .setTitle("Warning")
    .setMessage("Are you sure you want to delete this file?")
    .addButton("Cancel")
    .addButton("Delete")
    .build()
dialog.show()
Enter fullscreen mode Exit fullscreen mode

JavaScript:

class Dialog {
    constructor() {
        this.title = "";
        this.message = "";
        this.buttons = [];
    }

    show() {
        console.log(`Title: ${this.title}`);
        console.log(`Message: ${this.message}`);
        console.log(`Buttons: ${this.buttons.join(", ")}`);
    }
}

class DialogBuilder {
    constructor() {
        this.dialog = new Dialog();
    }

    setTitle(title) {
        this.dialog.title = title;
        return this;
    }

    setMessage(message) {
        this.dialog.message = message;
        return this;
    }

    addButton(button) {
        this.dialog.buttons.push(button);
        return this;
    }

    build() {
        return this.dialog;
    }
}

// Usage
const dialog = new DialogBuilder()
    .setTitle("Warning")
    .setMessage("Are you sure you want to delete this file?")
    .addButton("Cancel")
    .addButton("Delete")
    .build();
dialog.show();
Enter fullscreen mode Exit fullscreen mode

Abstract Factory

Provides an interface for creating families of related or dependent objects without specifying their concrete classes.
Use Case: Creating families of related or dependent objects without specifying their concrete classes.

Swift

protocol Button {
    func render()
}

class WindowsButton: Button {
    func render() {
        print("Render a button in Windows style")
    }
}

class MacOSButton: Button {
    func render() {
        print("Render a button in MacOS style")
    }
}

protocol GUIFactory {
    func createButton() -> Button
}

class WindowsFactory: GUIFactory {
    func createButton() -> Button {
        return WindowsButton()
    }
}

class MacOSFactory: GUIFactory {
    func createButton() -> Button {
        return MacOSButton()
    }
}

// Usage
func createUI(factory: GUIFactory) {
    let button = factory.createButton()
    button.render()
}

let windowsFactory = WindowsFactory()
createUI(factory: windowsFactory)

let macFactory = MacOSFactory()
createUI(factory: macFactory)
Enter fullscreen mode Exit fullscreen mode

JavaScript:

class WindowsButton {
    render() {
        console.log("Render a button in Windows style");
    }
}

class MacOSButton {
    render() {
        console.log("Render a button in MacOS style");
    }
}

class WindowsFactory {
    createButton() {
        return new WindowsButton();
    }
}

class MacOSFactory {
    createButton() {
        return new MacOSButton();
    }
}

// Usage
function createUI(factory) {
    const button = factory.createButton();
    button.render();
}

const windowsFactory = new WindowsFactory();
createUI(windowsFactory);

const macFactory = new MacOSFactory();
createUI(macFactory);
Enter fullscreen mode Exit fullscreen mode

Prototype

Creates new objects by copying an existing object, known as the prototype.
Use Case: Cloning objects to avoid the cost of creating new instances from scratch.

Swift

protocol Clonable {
    func clone() -> Self
}

class Prototype: Clonable {
    var name: String

    required init(name: String) {
        self.name = name
    }

    func clone() -> Self {
        return type(of: self).init(name: self.name)
    }
}

// Usage
let original = Prototype(name: "Original")
let copy = original.clone()
copy.name = "Changed"
print(original.name) // Output: Original
print(copy.name) // Output: Changed
Enter fullscreen mode Exit fullscreen mode

JavaScript:

class Prototype {
    constructor(name) {
        this.name = name;
    }

    clone() {
        return new Prototype(this.name);
    }
}

// Usage
const original = new Prototype("Original");
const copy = original.clone();
copy.name = "Changed";
console.log(original.name); // Output: Original
console.log(copy.name); // Output: Changed
Enter fullscreen mode Exit fullscreen mode

Top comments (3)

Collapse
 
namix profile image
Nick

There is a typo in your last code comment
console.log(copy.name); // Output: Original the output here is "Changed".

Collapse
 
andres_paladines profile image
andres paladines

Thanks for reading it till the last line <3

Collapse
 
andres_paladines profile image
andres paladines • Edited

Thanks Nick!
Changed! xD