/ 设计模式 / 代理模式 / Swift Swift 代理模式讲解和代码示例 代理是一种结构型设计模式, 让你能提供真实服务对象的替代品给客户端使用。 代理接收客户端的请求并进行一些处理 (访问控制和缓存等), 然后再将请求传递给服务对象。 代理对象拥有和服务对象相同的接口, 这使得当其被传递给客户端时可与真实对象互换。 进一步了解代理模式 导航 简介 概念示例 Example Output 真实世界示例 Example Output 复杂度: 流行度: 使用示例: 尽管代理模式在绝大多数 Swift 程序中并不常见, 但它在一些特殊情况下仍然非常方便。 当你希望在无需修改客户代码的前提下于已有类的对象上增加额外行为时, 该模式是无可替代的。 识别方法: 代理模式会将所有实际工作委派给一些其他对象。 除非代理是某个服务的子类, 否则每个代理方法最后都应该引用一个服务对象。 以下示例可在 Swift Playgrounds 上使用。 感谢 Alejandro Mohamad 创建了Playground版本。 概念示例 真实世界示例 概念示例 本例说明了代理设计模式的结构并重点回答了下面的问题: 它由哪些类组成? 这些类扮演了哪些角色? 模式中的各个元素会以何种方式相互关联? 了解该模式的结构后, 你可以更容易地理解下面基于真实世界的 Swift 应用案例。 Example.swift: 概念示例 import XCTest /// The Subject interface declares common operations for both RealSubject and /// the Proxy. As long as the client works with RealSubject using this /// interface, you'll be able to pass it a proxy instead of a real subject. protocol Subject { func request() } /// The RealSubject contains some core business logic. Usually, RealSubjects are /// capable of doing some useful work which may also be very slow or sensitive - /// e.g. correcting input data. A Proxy can solve these issues without any /// changes to the RealSubject's code. class RealSubject: Subject { func request() { print("RealSubject: Handling request.") } } /// The Proxy has an interface identical to the RealSubject. class Proxy: Subject { private var realSubject: RealSubject /// The Proxy maintains a reference to an object of the RealSubject class. /// It can be either lazy-loaded or passed to the Proxy by the client. init(_ realSubject: RealSubject) { self.realSubject = realSubject } /// The most common applications of the Proxy pattern are lazy loading, /// caching, controlling the access, logging, etc. A Proxy can perform one /// of these things and then, depending on the result, pass the execution to /// the same method in a linked RealSubject object. func request() { if (checkAccess()) { realSubject.request() logAccess() } } private func checkAccess() -> Bool { /// Some real checks should go here. print("Proxy: Checking access prior to firing a real request.") return true } private func logAccess() { print("Proxy: Logging the time of request.") } } /// The client code is supposed to work with all objects (both subjects and /// proxies) via the Subject interface in order to support both real subjects /// and proxies. In real life, however, clients mostly work with their real /// subjects directly. In this case, to implement the pattern more easily, you /// can extend your proxy from the real subject's class. class Client { // ... static func clientCode(subject: Subject) { // ... subject.request() // ... } // ... } /// Let's see how it all works together. class ProxyConceptual: XCTestCase { func test() { print("Client: Executing the client code with a real subject:") let realSubject = RealSubject() Client.clientCode(subject: realSubject) print("\nClient: Executing the same client code with a proxy:") let proxy = Proxy(realSubject) Client.clientCode(subject: proxy) } } Output.txt: 执行结果 Client: Executing the client code with a real subject: RealSubject: Handling request. Client: Executing the same client code with a proxy: Proxy: Checking access prior to firing a real request. RealSubject: Handling request. Proxy: Logging the time of request. 真实世界示例 Example.swift: 真实世界示例 import XCTest class ProxyRealWorld: XCTestCase { /// Proxy Design Pattern /// /// Intent: Provide a surrogate or placeholder for another object to control /// access to the original object or to add other responsibilities. /// /// Example: There are countless ways proxies can be used: caching, logging, /// access control, delayed initialization, etc. func testProxyRealWorld() { print("Client: Loading a profile WITHOUT proxy") loadBasicProfile(with: Keychain()) loadProfileWithBankAccount(with: Keychain()) print("\nClient: Let's load a profile WITH proxy") loadBasicProfile(with: ProfileProxy()) loadProfileWithBankAccount(with: ProfileProxy()) } func loadBasicProfile(with service: ProfileService) { service.loadProfile(with: [.basic], success: { profile in print("Client: Basic profile is loaded") }) { error in print("Client: Cannot load a basic profile") print("Client: Error: " + error.localizedSummary) } } func loadProfileWithBankAccount(with service: ProfileService) { service.loadProfile(with: [.basic, .bankAccount], success: { profile in print("Client: Basic profile with a bank account is loaded") }) { error in print("Client: Cannot load a profile with a bank account") print("Client: Error: " + error.localizedSummary) } } } enum AccessField { case basic case bankAccount } protocol ProfileService { typealias Success = (Profile) -> () typealias Failure = (LocalizedError) -> () func loadProfile(with fields: [AccessField], success: Success, failure: Failure) } class ProfileProxy: ProfileService { private let keychain = Keychain() func loadProfile(with fields: [AccessField], success: Success, failure: Failure) { if let error = checkAccess(for: fields) { failure(error) } else { /// Note: /// At this point, the `success` and `failure` closures can be /// passed directly to the original service (as it is now) or /// expanded here to handle a result (for example, to cache). keychain.loadProfile(with: fields, success: success, failure: failure) } } private func checkAccess(for fields: [AccessField]) -> LocalizedError? { if fields.contains(.bankAccount) { switch BiometricsService.checkAccess() { case .authorized: return nil case .denied: return ProfileError.accessDenied } } return nil } } class Keychain: ProfileService { func loadProfile(with fields: [AccessField], success: Success, failure: Failure) { var profile = Profile() for item in fields { switch item { case .basic: let info = loadBasicProfile() profile.firstName = info[Profile.Keys.firstName.raw] profile.lastName = info[Profile.Keys.lastName.raw] profile.email = info[Profile.Keys.email.raw] case .bankAccount: profile.bankAccount = loadBankAccount() } } success(profile) } private func loadBasicProfile() -> [String : String] { /// Gets these fields from a secure storage. return [Profile.Keys.firstName.raw : "Vasya", Profile.Keys.lastName.raw : "Pupkin", Profile.Keys.email.raw : "vasya.pupkin@gmail.com"] } private func loadBankAccount() -> BankAccount { /// Gets these fields from a secure storage. return BankAccount(id: 12345, amount: 999) } } class BiometricsService { enum Access { case authorized case denied } static func checkAccess() -> Access { /// The service uses Face ID, Touch ID or a plain old password to /// determine whether the current user is an owner of the device. /// Let's assume that in our example a user forgot a password :) return .denied } } struct Profile { enum Keys: String { case firstName case lastName case email } var firstName: String? var lastName: String? var email: String? var bankAccount: BankAccount? } struct BankAccount { var id: Int var amount: Double } enum ProfileError: LocalizedError { case accessDenied var errorDescription: String? { switch self { case .accessDenied: return "Access is denied. Please enter a valid password" } } } extension RawRepresentable { var raw: Self.RawValue { return rawValue } } extension LocalizedError { var localizedSummary: String { return errorDescription ?? "" } } Output.txt: 执行结果 Client: Loading a profile WITHOUT proxy Client: Basic profile is loaded Client: Basic profile with a bank account is loaded Client: Let's load a profile WITH proxy Client: Basic profile is loaded Client: Cannot load a profile with a bank account Client: Error: Access is denied. Please enter a valid password 概念示例 真实世界示例