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
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.