Skip to content

Bridge ​

Bridge separates two dimensions that can vary independently. One side is the abstraction the feature uses; the other is an implementation chosen at runtime.

iOS problem and when to use it ​

A reminders app has message types such as appointment and medication, and delivery channels such as local notification and in-app banner. Subclassing every combination creates a growing matrix. Bridge lets message formatting and delivery vary separately.

Original example: reminder and channel ​

swift
import Foundation

protocol ReminderChannel {
    func deliver(title: String, body: String)
}

struct InAppBannerChannel: ReminderChannel {
    func deliver(title: String, body: String) {
        print("Banner: \(title) — \(body)")
    }
}

struct LocalNotificationChannel: ReminderChannel {
    func deliver(title: String, body: String) {
        // Production implementation schedules UNNotificationRequest.
        print("Local notification: \(title) — \(body)")
    }
}

protocol Reminder {
    func send(via channel: any ReminderChannel)
}

struct AppointmentReminder: Reminder {
    let doctorName: String
    func send(via channel: any ReminderChannel) {
        channel.deliver(title: "Appointment", body: "Visit \(doctorName) tomorrow")
    }
}

struct MedicationReminder: Reminder {
    let medicineName: String
    func send(via channel: any ReminderChannel) {
        channel.deliver(title: "Medication", body: "Take \(medicineName)")
    }
}

let reminder: any Reminder = MedicationReminder(medicineName: "Vitamin D")
reminder.send(via: InAppBannerChannel())

The reminder decides what to say; the channel decides how it reaches the user. Adding a new channel does not require changing every reminder type.

Production decisions ​

Have the app choose a channel based on authorization, foreground state, and user preference. A real UNUserNotificationCenter adapter is asynchronous and can fail, so give the channel an async throws method if scheduling results matter. Do not promise that a local notification was shown merely because it was scheduled.

Two independent axes ​

Reminder owns message meaning: it knows whether the text is about an appointment or medication. ReminderChannel owns delivery: it knows whether the destination is an in-app banner or a local notification. send(via:) joins the two at runtime. If you add a third reminder type, existing channels work unchanged; if you add a third channel, existing reminders work unchanged. That avoids one class for every combination.

From example to an iOS screen ​

The app can choose a channel based on whether it is foregrounded and whether notification permission is granted. A foreground user might see a banner; a background reminder might be scheduled through UNUserNotificationCenter. The reminder model should carry localized message data or a localization key, while the channel handles platform delivery. For a real scheduling API, make deliver asynchronous and surface scheduling errors rather than printing.

Failure modes and verification ​

Do not put channel policy inside each reminder subtype; that reunites the two axes. Test every message type with a recording channel and test channel selection separately using authorization and foreground state. Bridge is most useful when both axes vary. If only delivery varies, a simple injected delivery service may be enough.

All patterns

Released under the MIT License.