Мост — это структурный паттерн, который разделяет бизнес-логику или большой класс на несколько отдельных иерархий, которые потом можно развивать отдельно друг от друга.
Одна из этих иерархий (абстракция) получит ссылку на объекты другой иерархии (реализация) и будет делегировать им основную работу. Благодаря тому, что все реализации будут следовать общему интерфейсу, их можно будет взаимозаменять внутри абстракции.
Применимость: Паттерн Мост особенно полезен когда вам приходится делать кросс-платформенные приложения, поддерживать несколько типов баз данных или работать с разными поставщиками похожего API (например, cloud-сервисы, социальные сети и т. д.)
Признаки применения паттерна: Если в программе чётко выделены классы «управления» и несколько видов классов «платформ», причём управляющие объекты делегируют выполнение платформам, то можно сказать, что у вас используется Мост.
Этот пример показывает структуру паттерна Мост, а именно — из каких классов он состоит, какие роли эти классы выполняют и как они взаимодействуют друг с другом.
После ознакомления со структурой, вам будет легче воспринимать второй пример, который рассматривает реальный случай использования паттерна в мире Swift.
Example.swift: Пример структуры паттерна
import XCTest /// Абстракция устанавливает интерфейс для «управляющей» части двух иерархий /// классов. Она содержит ссылку на объект из иерархии Реализации и делегирует /// ему всю настоящую работу. 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 } } /// Можно расширить Абстракцию без изменения классов Реализации. class ExtendedAbstraction: Abstraction { override func operation() -> String { let operation = implementation.operationImplementation() return "ExtendedAbstraction: Extended operation with:\n" + operation } } /// Реализация устанавливает интерфейс для всех классов реализации. Он не должен /// соответствовать интерфейсу Абстракции. На практике оба интерфейса могут быть /// совершенно разными. Как правило, интерфейс Реализации предоставляет только /// примитивные операции, в то время как Абстракция определяет операции более /// высокого уровня, основанные на этих примитивах. protocol Implementation { func operationImplementation() -> String } /// Каждая Конкретная Реализация соответствует определённой платформе и /// реализует интерфейс Реализации с использованием 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" } } /// За исключением этапа инициализации, когда объект Абстракции связывается с /// определённым объектом Реализации, клиентский код должен зависеть только от /// класса Абстракции. Таким образом, клиентский код может поддерживать любую /// комбинацию абстракции и реализации. class Client { // ... static func someClientCode(abstraction: Abstraction) { print(abstraction.operation()) } // ... } /// Давайте посмотрим как всё это будет работать. class BridgeConceptual: XCTestCase { func testBridgeConceptual() { // Клиентский код должен работать с любой предварительно // сконфигурированной комбинацией абстракции и реализации. 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