Most jest strukturalnym wzorcem projektowym zakładającym podział logiki biznesowej lub dużej klasy na osobne hierarchie klas które następnie można rozwijać niezależnie od siebie.
Jedna z takich hierarchii (zwana często Abstrakcją) posiada referencję do obiektu drugiej hierarchii (zwanej Implementacją) i deleguje mu część (czasem większość) wywołań. Ponieważ wszystkie implementacje mają wspólny interfejs, z punktu widzenia abstrakcji są wymienialne.
Przykłady użycia: Wzorzec Most jest szczególnie przydatny gdy trzeba wspierać obsługę wielu typów serwerów bazodanowych lub interfejsów programowania aplikacji danego typu (na przykład chmura, platformy społecznościowe, itd.)
Identyfikacja: Most można rozpoznać po wyraźnym rozdzieleniu na część kontrolującą i wiele różnych platform od których ta część zależy.
Poniższy przykład ilustruje strukturę wzorca Most ze szczególnym naciskiem na następujące kwestie:
Z jakich składa się klas?
Jakie role pełnią te klasy?
W jaki sposób elementy wzorca są ze sobą powiązane?
Poznawszy strukturę wzorca będzie ci łatwiej zrozumieć następujący przykład, oparty na prawdziwym przypadku użycia Swift.
Example.swift: Przykład koncepcyjny
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: Wynik działania
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
Przykład z prawdziwego życia
Example.swift: Przykład z prawdziwego życia
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: Wynik działania
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