Bridge is a structural design pattern that divides business logic or huge class into separate class hierarchies that can be developed independently.
One of these hierarchies (often called the Abstraction) will get a reference to an object of the second hierarchy (Implementation). The abstraction will be able to delegate some (sometimes, most) of its calls to the implementations object. Since all implementations will have a common interface, they’d be interchangeable inside the abstraction.
Conceptual Example
This example illustrates the structure of the Bridge design pattern and focuses on the following questions:
What classes does it consist of?
What roles do these classes play?
In what way the elements of the pattern are related?
After learning about the pattern’s structure it’ll be easier for you to grasp the following example, based on a real-world Swift use case.
Example.swift: Conceptual example
import XCTest
/// The Abstraction defines the interface for the "control" part of the two
/// class hierarchies. It maintains a reference to an object of the
/// Implementation hierarchy and delegates all of the real work to this object.
class Abstraction {
fileprivate var implementation: Implementation
init(_ implementation: Implementation) {
self.implementation = implementation
}
func operation() -> String {
let operation = implementation.operationImplementation()
return "Abstraction: Base operation with:\n" + operation
}
}
/// You can extend the Abstraction without changing the Implementation classes.
class ExtendedAbstraction: Abstraction {
override func operation() -> String {
let operation = implementation.operationImplementation()
return "ExtendedAbstraction: Extended operation with:\n" + operation
}
}
/// The Implementation defines the interface for all implementation classes. It
/// doesn't have to match the Abstraction's interface. In fact, the two
/// interfaces can be entirely different. Typically the Implementation interface
/// provides only primitive operations, while the Abstraction defines higher-
/// level operations based on those primitives.
protocol Implementation {
func operationImplementation() -> String
}
/// Each Concrete Implementation corresponds to a specific platform and
/// implements the Implementation interface using that platform's API.
class ConcreteImplementationA: Implementation {
func operationImplementation() -> String {
return "ConcreteImplementationA: Here's the result on the platform A.\n"
}
}
class ConcreteImplementationB: Implementation {
func operationImplementation() -> String {
return "ConcreteImplementationB: Here's the result on the platform B\n"
}
}
/// Except for the initialization phase, where an Abstraction object gets linked
/// with a specific Implementation object, the client code should only depend on
/// the Abstraction class. This way the client code can support any abstraction-
/// implementation combination.
class Client {
// ...
static func someClientCode(abstraction: Abstraction) {
print(abstraction.operation())
}
// ...
}
/// Let's see how it all works together.
class BridgeConceptual: XCTestCase {
func testBridgeConceptual() {
// The client code should be able to work with any pre-configured
// abstraction-implementation combination.
let implementation = ConcreteImplementationA()
Client.someClientCode(abstraction: Abstraction(implementation))
let concreteImplementation = ConcreteImplementationB()
Client.someClientCode(abstraction: ExtendedAbstraction(concreteImplementation))
}
}
Output.txt: Execution result
Abstraction: Base operation with:
ConcreteImplementationA: Here's the result on the platform A
ExtendedAbstraction: Extended operation with:
ConcreteImplementationB: Here's the result on the platform B
Real World Example
Example.swift: Real world example
import XCTest
private class BridgeRealWorld: XCTestCase {
func testBridgeRealWorld() {
print("Client: Pushing Photo View Controller...")
push(PhotoViewController())
print()
print("Client: Pushing Feed View Controller...")
push(FeedViewController())
}
func push(_ container: SharingSupportable) {
let instagram = InstagramSharingService()
let facebook = FaceBookSharingService()
container.accept(service: instagram)
container.update(content: foodModel)
container.accept(service: facebook)
container.update(content: foodModel)
}
var foodModel: Content {
return FoodDomainModel(title: "This food is so various and delicious!",
images: [UIImage(), UIImage()],
calories: 47)
}
}
private protocol SharingSupportable {
/// Abstraction
func accept(service: SharingService)
func update(content: Content)
}
class BaseViewController: UIViewController, SharingSupportable {
fileprivate var shareService: SharingService?
func update(content: Content) {
/// ...updating UI and showing a content...
/// ...
/// ... then, a user will choose a content and trigger an event
print("\(description): User selected a \(content) to share")
/// ...
shareService?.share(content: content)
}
func accept(service: SharingService) {
shareService = service
}
}
class PhotoViewController: BaseViewController {
/// Custom UI and features
override var description: String {
return "PhotoViewController"
}
}
class FeedViewController: BaseViewController {
/// Custom UI and features
override var description: String {
return "FeedViewController"
}
}
protocol SharingService {
/// Implementation
func share(content: Content)
}
class FaceBookSharingService: SharingService {
func share(content: Content) {
/// Use FaceBook API to share a content
print("Service: \(content) was posted to the Facebook")
}
}
class InstagramSharingService: SharingService {
func share(content: Content) {
/// Use Instagram API to share a content
print("Service: \(content) was posted to the Instagram", terminator: "\n\n")
}
}
protocol Content: CustomStringConvertible {
var title: String { get }
var images: [UIImage] { get }
}
struct FoodDomainModel: Content {
var title: String
var images: [UIImage]
var calories: Int
var description: String {
return "Food Model"
}
}
Output.txt: Execution result
Client: Pushing Photo View Controller...
PhotoViewController: User selected a Food Model to share
Service: Food Model was posted to the Instagram
PhotoViewController: User selected a Food Model to share
Service: Food Model was posted to the Facebook
Client: Pushing Feed View Controller...
FeedViewController: User selected a Food Model to share
Service: Food Model was posted to the Instagram
FeedViewController: User selected a Food Model to share
Service: Food Model was posted to the Facebook