Міст — це структурний патерн, який розділяє бізнес-логіку або великий клас на кілька окремих ієрархій, які можна розвивати далі окремо одну від одної.
Одна з цих ієрархій (абстракція) отримає посилання на об’єкти іншої ієрархії (реалізація) і буде делегувати їм основну роботу. Завдяки тому, що всі реалізації будуть дотримуватись спільного інтерфейсу, їх можна буде взаємозамінювати всередині абстракції.
Застосування: Патерн Міст особливо корисний, якщо вам доводиться робити крос-платформні додатки, підтримувати кілька типів баз даних або працювати з різними постачальниками схожого API (наприклад, cloud-сервіси, соціальні мережі і т. д.)
Ознаки застосування патерна: Якщо в програмі чітко виділено класи «керування» та кілька видів класів «платформ», а керуючі об’єкти делегують виконання платформам, тоді можна сказати, що ви застосовуєте Міст.
Цей приклад показує структуру патерна Міст, а саме — з яких класів він складається, які ролі ці класи виконують і як вони взаємодіють один з одним.
Після ознайомлення зі структурою, вам буде легше сприймати наступний приклад, що розглядає реальний випадок використання патерна в світі Swift.
Example.swift: Приклад структури патерна
import XCTest /// The Abstraction defines the interface for the "control" part of the two /// class hierarchies. It holds a reference to an object from 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: Результат виконання
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
Життєвий приклад
Example.swift: Життєвий приклад
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: Результат виконання
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