TechBlogs

๐Ÿงฉ Structural Design Pattern

๐Ÿ”Œ Adapter (Structural)

The Adapter pattern allows incompatible interfaces to work together by converting the interface of one class into an interface expected by the client. Itโ€™s like a power plug adapter that lets a U.S. charger work in a European socket.


โœ… When to Use


๐Ÿ’ก Real-World Example

๐ŸŽง Audio Player Adapter

protocol MediaPlayer {
    func play(filename: String)
}

class MP3Player: MediaPlayer {
    func play(filename: String) {
        print("๐ŸŽต Playing MP3 file: \(filename)")
    }
}

// Existing incompatible class
class VLCPlayer {
    func playVLC(file: String) {
        print("๐ŸŽฌ Playing VLC file: \(file)")
    }
}

// Adapter that makes VLCPlayer compatible
class VLCAdapter: MediaPlayer {
    private let vlcPlayer = VLCPlayer()
    
    func play(filename: String) {
        vlcPlayer.playVLC(file: filename)
    }
}

๐Ÿงช Usage

let mp3 = MP3Player()
mp3.play(filename: "song.mp3")

let vlcAdapter = VLCAdapter()
vlcAdapter.play(filename: "movie.vlc")

๐ŸŽ iOS Built-in Usage

iOS uses the Adapter pattern in many places:


๐Ÿงฉ Summary

Component Role
Target The interface the client expects
Adaptee The existing, incompatible interface
Adapter Translates Adaptee methods to Target interface

๐Ÿ”Œ The Adapter pattern promotes code reusability by allowing legacy or third-party code to be reused in modern systems without modification.


๐ŸŒ‰ Bridge (Structural)

๐Ÿง  Intent

The Bridge Pattern decouples an abstraction from its implementation so that the two can vary independently. Itโ€™s especially useful when you have multiple dimensions of variation in your system.


๐Ÿ’ก Real-World Example: Payment Gateway Integrations

Suppose youโ€™re building an e-commerce app that needs to support:

Hard-coding all combinations leads to a mess. With the Bridge pattern, we separate the payment logic (abstraction) from gateway implementations, keeping your system extensible and maintainable.


๐Ÿงฑ Structure

// MARK: - Implementation Interface
protocol PaymentGateway {
    func processPayment(amount: Double)
}

// MARK: - Concrete Implementations
class StripeGateway: PaymentGateway {
    func processPayment(amount: Double) {
        print("Processing โ‚น\(amount) via Stripe.")
    }
}

class PayPalGateway: PaymentGateway {
    func processPayment(amount: Double) {
        print("Processing โ‚น\(amount) via PayPal.")
    }
}

// MARK: - Abstraction
class Payment {
    let gateway: PaymentGateway

    init(gateway: PaymentGateway) {
        self.gateway = gateway
    }

    func makePayment(amount: Double) {
        gateway.processPayment(amount: amount)
    }
}

// MARK: - Refined Abstractions
class SubscriptionPayment: Payment {
    override func makePayment(amount: Double) {
        print("Handling subscription logic...")
        super.makePayment(amount: amount)
    }
}

class OneTimePayment: Payment {
    override func makePayment(amount: Double) {
        print("Handling one-time payment logic...")
        super.makePayment(amount: amount)
    }
}

โœ… Usage

let stripe = StripeGateway()
let paypal = PayPalGateway()

let monthlySub = SubscriptionPayment(gateway: stripe)
monthlySub.makePayment(amount: 999.0)

// Output:
// Handling subscription logic...
// Processing โ‚น999.0 via Stripe.

let oneTime = OneTimePayment(gateway: paypal)
oneTime.makePayment(amount: 499.0)

// Output:
// Handling one-time payment logic...
// Processing โ‚น499.0 via PayPal.

๐ŸŽ iOS Usage Analogy

๐ŸŒฟ Composite (Structural)

๐Ÿง  Definition:

The Composite Pattern lets you treat individual objects and compositions of objects uniformly. Itโ€™s perfect for representing hierarchical, tree-like structures where you want to work with both single items and groups in the same way.


๐Ÿงพ Real-World Example:

File System


๐Ÿ“ฑ iOS Real Example:


๐Ÿง‘โ€๐Ÿ’ป Swift Example:

// Component Protocol
protocol FileSystemItem {
    var name: String { get }
    func display(indentation: String)
}

// Leaf
struct File: FileSystemItem {
    let name: String
    func display(indentation: String) {
        print("\(indentation)๐Ÿ“„ \(name)")
    }
}

// Composite
class Folder: FileSystemItem {
    let name: String
    private var items: [FileSystemItem] = []

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

    func add(_ item: FileSystemItem) {
        items.append(item)
    }

    func display(indentation: String = "") {
        print("\(indentation)๐Ÿ“‚ \(name)")
        items.forEach { $0.display(indentation: indentation + "  ") }
    }
}

// Usage
let file1 = File(name: "Resume.pdf")
let file2 = File(name: "CoverLetter.pdf")
let docsFolder = Folder(name: "Documents")
docsFolder.add(file1)
docsFolder.add(file2)

let rootFolder = Folder(name: "Home")
rootFolder.add(docsFolder)
rootFolder.display()

Output:

๐Ÿ“‚ Home
  ๐Ÿ“‚ Documents
    ๐Ÿ“„ Resume.pdf
    ๐Ÿ“„ CoverLetter.pdf

๐Ÿงฐ When to Use:


๐ŸŽจ Decorator (Structural)

๐Ÿง  Definition The Decorator Pattern allows you to add new behavior to an object dynamically without modifying its existing code. It wraps the original object inside another object that adds the extra functionality โ€” just like adding toppings to ice cream without changing the ice cream itself. This provides a flexible alternative to using inheritance to modify behaviour ๐Ÿ“ฑ iOS Real Example

Here are Top 5 real-world & iOS relatable examples for the ๐Ÿง Decorator Pattern you can choose from:

  1. ๐Ÿ“„ NSAttributedString Styling โ€“ Base text with dynamic attributes (color, font, underline, background, kerning).
  2. โ˜• Coffee Ordering System โ€“ Base drink with add-ons like milk, whipped cream, syrups, extra shots.
  3. ๐ŸŽจ SwiftUI View Modifiers โ€“ Applying .padding(), .background(), .shadow() as chained decorators.
  4. ๐Ÿ”’ Network Request Handling โ€“ Wrapping API calls with decorators for logging, authentication, retry logic, caching.
  5. ๐ŸŽฎ Game Character Power-ups โ€“ Base character gets temporary abilities like shields, speed boosts, invisibility.

๐ŸŽฌ Decorator Pattern โ€” Video Streaming Player (Scalable Example)

The Decorator Pattern is perfect for a streaming player: you keep a simple base player implementation and attach features (subtitles, watermark overlays, analytics, adaptive bitrate, etc.) at runtime โ€” without changing the player itself. This yields a highly modular and scalable architecture: add/remove features independently, compose them in any order, and test each feature in isolation.


๐Ÿง  Concept


๐Ÿ“ฆ Components


๐Ÿ’ป Swift Example

import Foundation
import CoreGraphics

// MARK: - Player Protocol (Component)
protocol Player {
    func play(url: URL)
    func stop()
    func seek(to seconds: Double)
}

// MARK: - Basic Player (Concrete Component)
class BasicPlayer: Player {
    func play(url: URL) {
        print("โ–ถ๏ธ Playing video at \(url.absoluteString)")
        // actual playback logic (AVPlayer, etc.) goes here
    }

    func stop() {
        print("โน Stopped playback")
    }

    func seek(to seconds: Double) {
        print("โฑ Seek to \(seconds)s")
    }
}

// MARK: - Decorator Base
class PlayerDecorator: Player {
    private let wrapped: Player

    init(wrapping player: Player) {
        self.wrapped = player
    }

    func play(url: URL) {
        wrapped.play(url: url)
    }

    func stop() {
        wrapped.stop()
    }

    func seek(to seconds: Double) {
        wrapped.seek(to: seconds)
    }
}

// MARK: - Subtitles Decorator
class SubtitlesDecorator: PlayerDecorator {
    private let subtitleURL: URL

    init(wrapping player: Player, subtitleURL: URL) {
        self.subtitleURL = subtitleURL
        super.init(wrapping: player)
    }

    override func play(url: URL) {
        loadSubtitles()
        super.play(url: url)
    }

    private func loadSubtitles() {
        print("๐Ÿ’ฌ Loading subtitles from \(subtitleURL.lastPathComponent)")
        // parse and attach subtitle rendering to player UI
    }
}

// MARK: - Watermark Decorator
class WatermarkDecorator: PlayerDecorator {
    private let watermarkText: String
    private let position: CGPoint

    init(wrapping player: Player, text: String, position: CGPoint = .init(x: 10, y: 10)) {
        self.watermarkText = text
        self.position = position
        super.init(wrapping: player)
    }

    override func play(url: URL) {
        addWatermark()
        super.play(url: url)
    }

    private func addWatermark() {
        print("๐Ÿ”– Adding watermark '\(watermarkText)' at \(position)")
        // overlay watermark view on player layer
    }
}

// MARK: - Analytics Decorator
class AnalyticsDecorator: PlayerDecorator {
    override func play(url: URL) {
        trackEvent("play", url: url)
        super.play(url: url)
    }

    override func stop() {
        trackEvent("stop", url: nil)
        super.stop()
    }

    override func seek(to seconds: Double) {
        trackEvent("seek", url: nil, extra: ["position": seconds])
        super.seek(to: seconds)
    }

    private func trackEvent(_ name: String, url: URL?, extra: [String: Any]? = nil) {
        print("๐Ÿ“Š Analytics - event: \(name), url: \(url?.lastPathComponent ?? "-"), extra: \(extra ?? [:])")
        // forward to analytics backend (batching, retries)
    }
}

// MARK: - Adaptive Bitrate Decorator
class AdaptiveBitrateDecorator: PlayerDecorator {
    override func play(url: URL) {
        selectInitialQuality()
        super.play(url: url)
        monitorNetworkAndAdapt()
    }

    private func selectInitialQuality() {
        print("โš™๏ธ Selecting initial bitrate based on current conditions")
    }

    private func monitorNetworkAndAdapt() {
        print("โš™๏ธ Monitoring network and adapting bitrate (simulated)")
        // In real app: observe bandwidth, switch streams or set AVPlayer item variants
    }
}

๐Ÿงช Usage Example

let url = URL(string: "https://cdn.example.com/movie.m3u8")!
let subtitleURL = URL(string: "https://cdn.example.com/movie.en.vtt")!

// Start with basic player
var player: Player = BasicPlayer()

// Dynamically compose decorators (order can matter: analytics wrapped outside will see all events)
player = SubtitlesDecorator(wrapping: player, subtitleURL: subtitleURL)
player = WatermarkDecorator(wrapping: player, text: "MyBrand")
player = AdaptiveBitrateDecorator(wrapping: player)
player = AnalyticsDecorator(wrapping: player)

// Use player
player.play(url: url)
player.seek(to: 120)
player.stop()

Sample Console Output:

๐Ÿ’ฌ Loading subtitles from movie.en.vtt
๐Ÿ”– Adding watermark 'MyBrand' at (10.0, 10.0)
โš™๏ธ Selecting initial bitrate based on current conditions
๐Ÿ“Š Analytics - event: play, url: movie.m3u8, extra: [:]
โ–ถ๏ธ Playing video at https://cdn.example.com/movie.m3u8
๐Ÿ“Š Analytics - event: seek, url: -, extra: ["position": 120]
โน Stopped playback
๐Ÿ“Š Analytics - event: stop, url: -

๐Ÿ—๏ธ Scaling Tips & Notes


โœ… Why Decorator fits video players

๐Ÿงฐ When to Use

๐Ÿ›๏ธ Faรงade (Structural)

Definition: The Faรงade pattern provides a unified, simplified interface to a set of complex subsystems, making them easier to use without exposing the underlying complexity. It acts as a โ€œfront deskโ€ that delegates requests to the appropriate backend services.


๐Ÿ“ฑ iOS Real Example โ€“ Media Upload Manager

Imagine a social media app where users can upload videos with multiple processing steps:

Without Faรงade, the ViewModel or Controller would need to orchestrate all these steps individually, increasing complexity and making the code harder to maintain.

With Faรงade:

import Foundation

// MARK: - Subsystem Placeholders
struct VideoCompressor {
    func compress(_ url: URL) -> URL {
        print("Compressing video...")
        return url // Placeholder
    }
}

struct VideoEncoder {
    func encode(_ url: URL) -> URL {
        print("Encoding video...")
        return url // Placeholder
    }
}

struct ThumbnailGenerator {
    func generate(from url: URL) {
        print("Generating thumbnails...")
    }
}

struct MetadataExtractor {
    func extract(from url: URL) {
        print("Extracting metadata...")
    }
}

struct CDNUploader {
    func upload(_ url: URL) {
        print("Uploading to CDN...")
    }
}

// MARK: - Facade
class MediaUploadFacade {
    private let compressor = VideoCompressor()
    private let encoder = VideoEncoder()
    private let thumbnailGen = ThumbnailGenerator()
    private let metadataExtractor = MetadataExtractor()
    private let uploader = CDNUploader()

    func uploadVideo(_ fileURL: URL) {
        let compressed = compressor.compress(fileURL)
        let encoded = encoder.encode(compressed)
        thumbnailGen.generate(from: encoded)
        metadataExtractor.extract(from: encoded)
        uploader.upload(encoded)
    }
}

// MARK: - Usage
let videoURL = URL(fileURLWithPath: "/path/to/video.mp4")
let mediaUploader = MediaUploadFacade()
mediaUploader.uploadVideo(videoURL)

Usage

let mediaUploader = MediaUploadFacade()
mediaUploader.uploadVideo(videoURL)

โœ… Benefits:


Why Scalable?

In a production app like TikTok or Instagram:


๐Ÿƒ Flyweight(Structural)

๐Ÿ“– Definition - The Flyweight pattern minimizes memory usage by sharing common, immutable data between multiple objects, instead of duplicating it.โ€จItโ€™s especially useful when working with a large number of similar objects.

Hereโ€™s a focused Flyweight Pattern implementation for E-commerce Product Options with memory impact analysis:

## Example: Text Formatting in a Document Editor
import Foundation

// Flyweight protocol
protocol TextFormatting {
    func apply(to character: Character)
}

// Concrete Flyweight
class CharacterFormatting: TextFormatting, Hashable {
    let fontName: String
    let fontSize: CGFloat
    let color: UIColor
    let isBold: Bool
    let isItalic: Bool
    
    init(fontName: String, fontSize: CGFloat, color: UIColor, isBold: Bool, isItalic: Bool) {
        self.fontName = fontName
        self.fontSize = fontSize
        self.color = color
        self.isBold = isBold
        self.isItalic = isItalic
    }
    
    func apply(to character: Character) {
        print("Displaying '\(character)' with: \(fontName), \(fontSize)pt, \(color), bold: \(isBold), italic: \(isItalic)")
    }
    
    // Hashable conformance for use in a Set/Dictionary
    func hash(into hasher: inout Hasher) {
        hasher.combine(fontName)
        hasher.combine(fontSize)
        hasher.combine(color)
        hasher.combine(isBold)
        hasher.combine(isItalic)
    }
    
    static func == (lhs: CharacterFormatting, rhs: CharacterFormatting) -> Bool {
        return lhs.fontName == rhs.fontName &&
               lhs.fontSize == rhs.fontSize &&
               lhs.color == rhs.color &&
               lhs.isBold == rhs.isBold &&
               lhs.isItalic == rhs.isItalic
    }
}

// Flyweight Factory
class FormattingFactory {
    private var formats: [CharacterFormatting] = []
    
    func getFormatting(fontName: String, fontSize: CGFloat, color: UIColor, isBold: Bool, isItalic: Bool) -> CharacterFormatting {
        let newFormat = CharacterFormatting(
            fontName: fontName,
            fontSize: fontSize,
            color: color,
            isBold: isBold,
            isItalic: isItalic
        )
        
        if let existingFormat = formats.first(where: { $0 == newFormat }) {
            return existingFormat
        }
        
        formats.append(newFormat)
        return newFormat
    }
    
    var totalFormatsCreated: Int {
        return formats.count
    }
}

// Client code - Document that uses formatted characters
struct FormattedCharacter {
    let character: Character
    let formatting: CharacterFormatting
    
    func display() {
        formatting.apply(to: character)
    }
}

class Document {
    private var characters: [FormattedCharacter] = []
    private let formattingFactory = FormattingFactory()
    
    func addCharacter(_ char: Character, fontName: String, fontSize: CGFloat, color: UIColor, isBold: Bool, isItalic: Bool) {
        let formatting = formattingFactory.getFormatting(
            fontName: fontName,
            fontSize: fontSize,
            color: color,
            isBold: isBold,
            isItalic: isItalic
        )
        
        let formattedChar = FormattedCharacter(character: char, formatting: formatting)
        characters.append(formattedChar)
    }
    
    func display() {
        characters.forEach { $0.display() }
    }
    
    func totalUniqueFormats() -> Int {
        return formattingFactory.totalFormatsCreated
    }
}

// Usage Example
let document = Document()

// Add many characters with different formatting
document.addCharacter("H", fontName: "Helvetica", fontSize: 12, color: .black, isBold: true, isItalic: false)
document.addCharacter("e", fontName: "Helvetica", fontSize: 12, color: .black, isBold: true, isItalic: false)
document.addCharacter("l", fontName: "Helvetica", fontSize: 12, color: .black, isBold: true, isItalic: false)
document.addCharacter("l", fontName: "Helvetica", fontSize: 12, color: .black, isBold: true, isItalic: false)
document.addCharacter("o", fontName: "Helvetica", fontSize: 12, color: .black, isBold: true, isItalic: false)
document.addCharacter(" ", fontName: "Helvetica", fontSize: 12, color: .black, isBold: false, isItalic: false)
document.addCharacter("W", fontName: "Times New Roman", fontSize: 14, color: .blue, isBold: false, isItalic: true)
document.addCharacter("o", fontName: "Times New Roman", fontSize: 14, color: .blue, isBold: false, isItalic: true)
document.addCharacter("r", fontName: "Times New Roman", fontSize: 14, color: .blue, isBold: false, isItalic: true)
document.addCharacter("l", fontName: "Times New Roman", fontSize: 14, color: .blue, isBold: false, isItalic: true)
document.addCharacter("d", fontName: "Times New Roman", fontSize: 14, color: .blue, isBold: false, isItalic: true)

document.display()
print("Total unique formats created: \(document.totalUniqueFormats())")

Key Points:

  1. Flyweight (TextFormatting protocol): Defines the interface for formatting objects
  2. Concrete Flyweight (CharacterFormatting): Implements the formatting and stores intrinsic state (shared data)
  3. Flyweight Factory (FormattingFactory): Manages and reuses flyweight objects
  4. Client (Document): Maintains references to flyweights and adds extrinsic state (character data)

Benefits:

This pattern is especially useful in iOS/macOS apps where you might have many UI elements with similar styling.


๐Ÿ”’ Protection Proxy (Structural)

๐Ÿ“– Definition

A Protection Proxy controls access to an object by checking permissions, authentication, or other access rules before allowing operations on it. It acts like a gatekeeper โ€” requests pass through it, and it decides whether to forward them to the real object.


๐ŸŽฏ Real-World Analogy

Think of a nightclub bouncer.


๐Ÿ“ฑ iOS Real Example

Imagine an admin settings screen in an iOS app:


๐Ÿ›  Usage Example (Compiling Swift Demo)

protocol AdminSettings {
    func updateSystemConfig()
}

// Real object
class RealAdminSettings: AdminSettings {
    func updateSystemConfig() {
        print("โœ… System configuration updated successfully!")
    }
}

// Protection Proxy
class AdminSettingsProxy: AdminSettings {
    private let realSettings = RealAdminSettings()
    private let currentUserRole: String
    
    init(userRole: String) {
        self.currentUserRole = userRole
    }
    
    func updateSystemConfig() {
        guard currentUserRole == "admin" else {
            print("โŒ Access Denied: You are not authorized to update system configuration.")
            return
        }
        realSettings.updateSystemConfig()
    }
}

// Usage
let adminUser = AdminSettingsProxy(userRole: "admin")
adminUser.updateSystemConfig()  // โœ… Allowed

let guestUser = AdminSettingsProxy(userRole: "guest")
guestUser.updateSystemConfig()  // โŒ Denied

๐Ÿ’ก Why Use It in iOS?


๐Ÿ‘ป Virtual Proxy (Structural)

๐Ÿ“– Definition

A Virtual Proxy acts as a placeholder for a resource-heavy object, creating it only when itโ€™s actually needed. Itโ€™s like saying: โ€œDonโ€™t make the expensive thing until someone actually asks for it.โ€


๐ŸŽฏ Real-World Analogy

Think of a restaurant menu picture:


๐Ÿ“ฑ iOS Real Example

In an iOS photo gallery app:


๐Ÿ›  Usage Example (Compiling Swift Demo)

protocol HighResImage {
    func display()
}

// Real heavy object
class RealHighResImage: HighResImage {
    private let filename: String
    
    init(filename: String) {
        self.filename = filename
        loadFromDisk()
    }
    
    private func loadFromDisk() {
        print("๐Ÿ“‚ Loading high-res image from disk: \(filename)")
    }
    
    func display() {
        print("๐Ÿ–ผ Displaying high-res image: \(filename)")
    }
}

// Virtual Proxy
class ImageProxy: HighResImage {
    private let filename: String
    private var realImage: RealHighResImage?
    
    init(filename: String) {
        self.filename = filename
    }
    
    func display() {
        if realImage == nil {
            realImage = RealHighResImage(filename: filename)
        }
        realImage?.display()
    }
}

// Usage
let image = ImageProxy(filename: "big_photo.png")
print("๐Ÿ“ฑ App loaded, image not yet displayed.")
image.display()  // Loads on demand

๐Ÿ’ก Why Use It in iOS?