静态工厂 静态工厂模式的三个好处
命名构造器 静态工厂模式的头等好处是:每一个静态工厂方法都有一个自己的名字。Apple 在 UIColor
类实现中使用这个模式创建了许多命名颜色,比如 .red
, .yellow
,等。 注意,Swift
中的这种实现并非是方法,而是静态属性
,返回一个实际的实例。
1 2 3 4 5 6 7 public extension TimeInterval { public static var second: TimeInterval { return 1 } public static var minute: TimeInterval { return 60 } public static var hour: TimeInterval { return 3_600 } public static var day: TimeInterval { return 86_400 } public static var week: TimeInterval { return 604_800 } }
记住一天或者一周有多少秒很困难吧?所以 TimeInterval.week
比604_80
好多了。
缓存对象 静态工厂模式的第二个好处是:出于优化内存的考虑,你可以用它来实现缓存。利用静态构造器,你可以限制创建对象的数量。(即,静态工厂方法)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 class Service { var name: String private static var cache: [String :Service ] = [:] private static func cached (name : String ) -> Service { if Service .cache[name] == nil { Service .cache[name] = Service (named: name) } return Service .cache[name]! } static var local: Service { return Service .cached(name: "local" ) } static var remote: Service { return Service .cached(name: "remote" ) } init (named name : String ) { self .name = name } }
私有化构造域 静态工厂的另一个好处是你可以利用它将一个类的构造过程限制在 private
访问级别。换句话说,创建过程只能通过静态工厂方法来完成。你只需要将 init
方法声明为 private
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public final class Service { var name: String private init (name : String ) { self .name = name } public static var local: Service { return Service (name: "local" ) } public static var remote: Service { return Service (name: "remote" ) } }
注意,你可以利用 final
和 static
关键字来限制继承。如果要开放继承,你需要移除 final
关键字,同时在属性中用 class
关键字替换 static
,这样子类才能重写工厂方法。
静态方式返回 可以用静态方法返回任何类型,这种用法跟静态工厂很相似,应用场景十分广泛。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 extension UIColor { private static func image (with color : UIColor ) -> UIImage { let rect = CGRect (x: 0 , y: 0 , width: 1 , height: 1 ) UIGraphicsBeginImageContext (rect.size) let context = UIGraphicsGetCurrentContext ()! context.setFillColor(color.cgColor) context.fill(rect) let img = UIGraphicsGetImageFromCurrentImageContext () UIGraphicsEndImageContext () return img! } static var redImage: UIImage { return UIColor .image(with: .red) } }
工厂方法 工厂方法只是一个非静态方法,通常是由简单的协议和类实现 的。让我们从一个最简单的粒子开始吧:想象一个类,它能为你的服务终端创建一个 URL
,姑且称它为服务工厂吧。
1 2 3 4 5 6 7 class ServiceFactory { func createProductionUrl () -> URL { return URL (string: "https://localhost/" )! } } let factory = ServiceFactory ()factory.createProductionUrl()
再加一些东西,一个服务类的协议和一个返回 url
的协议。现在我们可以单独实现基础的 url
协议,并且从产品服务工厂那获得特定的实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 protocol ServiceFactory { func create () -> Service } protocol Service { var url: URL { get } } class ProductionService : Service { var url: URL { return URL (string: "https://localhost/" )! } } class ProductionServiceFactory : ServiceFactory { func create () -> Service { return ProductionService () } } let factory = ProductionServiceFactory ()let request = factory.create()
从现在开始,你可以轻松地实现一个模拟的服务,返回一个虚拟的url,用这个url来测试代码。明显地,这样将需要用到一个与服务匹配的工厂类。
抽象工厂 抽象工厂提供一种封装一组独立工厂的方式,这些独立工厂都有一个共同的主题。
抽象工厂的实现通常结合了简单工厂和工厂方法的准则。工厂方法创建独立的对象,然后所有的事情再经过一个“抽象的”的简单工厂包装起来。让我们一起来看下范例代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 protocol ServiceFactory { func create () -> Service } protocol Service { var url: URL { get } } class StagingService : Service { var url: URL { return URL (string: "https://dev.localhost/" )! } } class StagingServiceFactory : ServiceFactory { func create () -> Service { return StagingService () } } class ProductionService : Service { var url: URL { return URL (string: "https://live.localhost/" )! } } class ProductionServiceFactory : ServiceFactory { func create () -> Service { return ProductionService () } } class AppServiceFactory : ServiceFactory { enum Environment { case production case staging } var env: Environment init (env : Environment ) { self .env = env } func create () -> Service { switch self .env { case .production: return ProductionServiceFactory ().create() case .staging: return StagingServiceFactory ().create() } } } let factory = AppServiceFactory (env: .production)let service = factory.create()print (service.url)
使用抽象工厂将会影响整个应用的逻辑,而工厂方法只影响到局部。在实现抽象工厂的时候,也可以为它创建一个单独的接口。
抽象工厂通常用于实现对象的独立。举个例子,如果你有多个不同的SQL数据库连接器(PostgreSQL, MySQL等),在 Swift 中用一个通用接口表示,你可以利用抽象工厂在它们之间轻松地切换。
比较工厂设计模式 将用到 UIColor
来演示一些基础概念
静态工厂
没有独立的工厂类
通过命名的静态方法来初始化对象
可缓存,可返回子类型
1 2 3 4 5 6 7 extension UIColor { static var primary: UIColor { return .black } static var secondary: UIColor { return .white } } let primary = UIColor .primarylet secondary = UIColor .secondary
简单工厂
一个工厂类
内部用 switch case 创建不同对象
包装不同的代码
如果代码量太大,可以改成工厂方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class ColorFactory { enum Style { case primary case secondary } func create (_ style : Style ) { switch style case .primary: return .black case .secondary: return .white } } let factory = ColorFactory ()let primary = factory.create(.primary)let secondary = factory.create(.secondary)
工厂方法
多个 (解耦的) 工厂类
每个工厂方法创建一个实例
为工厂创建一个简单的协议
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 protocol ColorFactory { func create () -> UIColor } class PrimaryColorFactory : ColorFactory { func create () -> UIColor { return .black } } class SecondaryColorFactory : ColorFactory { func create () -> UIColor { return .white } } let primaryColorFactory = PrimaryColorFactory ()let secondaryColorFactory = SecondaryColorFactory ()let primary = primaryColorFactory.create()let secondary = secondaryColorFactory.create()
抽象工厂
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 protocol ColorFactory { func create () -> UIColor } class PrimaryColorFactory : ColorFactory { func create () -> UIColor { return .black } } class SecondaryColorFactory : ColorFactory { func create () -> UIColor { return .white } } class AppColorFactory : ColorFactory { enum Theme { case dark case light } func create (_ theme : Theme ) -> UIColor { switch theme { case .dark: return PrimaryColorFactory ().create() case .light: return SecondaryColorFactory ().create() } } } let factory = AppColorFactory ()let primaryColor = factory.create(.dark)let secondaryColor = factory.create(.light)
依赖注入 依赖注入(以下简称 DI
)指的是为一个对象添加实例变量 。学习 DI 无关乎实现细节,只关乎你如何运用这个模式。DI
有四种小变体
依赖注入基础 DI
的主要目标是:尽可能创建互相独立的对象,或者说尽可能松散地组织代码,以便提高代码的可复用性和可测试性。
Swift中如何实现DI 1 2 3 4 5 protocol Encoder { func encode <T >(_ value : T ) throws -> Data where T : Encodable } extension JSONEncoder : Encoder { }extension PropertyListEncoder : Encoder { }
Property
列表和 JSON
编码器已经实现这个协议的方法。我们只需要扩展我们的对象,令扩展的部分遵循这个协议。
构造器注入 DI
的最常见形式是构造器注入或者基于构造器的注入,其理念是,你通过构造器传入依赖,然后把依赖存在一个内部私有的只读属性变量中 。这种方式的好处是,你的对象与依赖相关的工作逻辑总是能正常工作,因为当它被创建的时候,依赖也被创建了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 class Post : Encodable { var title: String var content: String private var encoder: Encoder private enum CodingKeys : String , CodingKey { case title case content } init (title : String , content : String , encoder : Encoder ) { self .title = title self .content = content self .encoder = encoder } func encoded () throws -> Data { return try self .encoder.encode(self ) } } let post = Post (title: "Hello DI!" , content: "构造器注入" , encoder: JSONEncoder ())if let data = try? post.encoded(), let encoded = String (data: data, encoding: .utf8) { print (encoded) }
你也可以为构造器中的 encoder
提供一个默认值,但请千万小心”私生子注入“这种反面模式。这种模式在这里特指如果默认自来自另外一个模块,你的代码将不得不同那个模块耦合。三思而后行!
属性注入 有的时候构造器注入不容易实现,因为你的类需要继承自系统类,如果这个类还要跟视图或者控制器一起工作,注入将变得十分困难。这种情况下更好的解决方案是使用基于属性的注入模式。也许你无法完全掌控初始化过程,但你总是可以控制属性。这种模式唯一的缺点是,每次使用之前,你需要检查目标属性是否存在。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 class Post : Encodable { var title: String var content: String var encoder: Encoder ? private enum CodingKeys : String , CodingKey { case title case content } init (title : String , content : String ) { self .title = title self .content = content } func encoded () throws -> Data { guard let encoder = self .encoder else { fatalError ("Encoding is only supported with a valid encoder object." ) } return try encoder.encode(self ) } } let post = Post (title: "Hello DI!" , content: "属性注入" )post.encoder = JSONEncoder () if let data = try? post.encoded(), let encoded = String (data: data, encoding: .utf8) { print (encoded) }
iOS框架中大量应用了属性注入模式。委托代理模式也经常通过这种方式实现
方法注入 如果只需要用到某个依赖一次 ,你并不需要将它存储为对象的变量。与其用构造器参数或者对外暴露的可变属性,不如将依赖作为方法参数传入。这种方法被称为方法注入或者说基于参数的注入。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class Post : Encodable { var title: String var content: String init (title : String , content : String ) { self .title = title self .content = content } func encode (using encoder : Encoder ) throws -> Data { return try encoder.encode(self ) } } let post = Post (title: "Hello DI!" , content: "方法注入" )if let data = try? post.encode(using: JSONEncoder ()), let encoded = String (data: data, encoding: .utf8) { print (encoded) }
每当你的方法被调用时,你的注入都可以改变。由于不持有依赖的引用,这种依赖只能被用于本地方法内部。
即时上下文 只应当用于多个对象实例间共享的通用依赖 ,比如说日志、分析或者缓存等。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 class Post : Encodable { var title: String var content: String init (title : String , content : String ) { self .title = title self .content = content } func encoded () throws -> Data { return try Post .encoder.encode(self ) } private static var _encoder: Encoder = PropertyListEncoder () static func setEncoder (_ encoder : Encoder ) { self ._encoder = encoder } static var encoder: Encoder { return Post ._encoder } } let post = Post (title: "Hello DI!" , content: "即时上下文" )Post .setEncoder(JSONEncoder ())if let data = try? post.encoded(), let encoded = String (data: data, encoding: .utf8) { print (encoded) }
即时上下文有一些缺陷,它可能很好地解决了某些横跨多个关注点的情形,但同时也隐式地创造了可以全局修改的依赖状态。因此,情况允许的话你应该优先考虑其他的依赖注入方式。
对象池 对象池模式一种创建设计模式。它的主要理念是创建一组对象,即一个对象的池子,然后从这个池子中请求和释放对象,以取代直接不断创建和释放这些对象的方式。
Dispatch
这个框架就采用了对象池模式,它为开发者提供了预先创建好的队列,因为创建队列本身需要比较昂贵的开销。
那这个模式有没有什么弊端呢?在多线程环境下,你还得留心线程安全问题。
这里有一个简单的线程安全的泛型对象池的例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 import Foundationclass Pool <T > { private let lockQueue = DispatchQueue (label: "pool.lock.queue" ) private let semaphore: DispatchSemaphore private var items = [T ]() init (_ items : [T ]) { self .semaphore = DispatchSemaphore (value: items.count) self .items.reserveCapacity(items.count) self .items.append(contentsOf: items) } func acquire () -> T ? { if self .semaphore.wait(timeout: .distantFuture) == .success, ! self .items.isEmpty { return self .lockQueue.sync { return self .items.remove(at: 0 ) } } return nil } func release (_ item : T ) { self .lockQueue.sync { self .items.append(item) self .semaphore.signal() } } } let pool = Pool <String >(["a" , "b" , "c" ])let a = pool.acquire()print ("\(a ?? "n/a" ) 被请求" )let b = pool.acquire()print ("\(b ?? "n/a" ) 被请求" )let c = pool.acquire()print ("\(c ?? "n/a" ) acquired" )DispatchQueue .global(qos: .default).asyncAfter(deadline: .now() + .seconds(2 )) { if let item = b { pool.release(item) } } print ("对象池没有可用资源,阻塞线程。" )let x = pool.acquire()print ("\(x ?? "n/a" ) 被再次请求" )
你得到一个线程安全的泛型对象池。当池子里没有对象时,分发信号量将阻塞。整个池子依靠请求和释放两个方法工作。
在上面的例子中你可以看到,当池中没有对象可用时,队列将阻塞,直到有对象被释放为止。因此,使用对象池时,当心主线程被阻塞。
原型 原型设计模式用于创建基础对象的复制。
当你有一组对象的基础配置时,你需要复制这些预定义的值给一个新的对象。这个过程,你其实正在复制原型对象。
这种方法有一个好处,你不需要使用子类,而是通过独立的复制配置对象,这样可以避免一些样板代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 class Paragraph { var font: UIFont var color: UIColor var text: String init (font : UIFont = UIFont .systemFont(ofSize: 12 ), color : UIColor = .darkText, text : String = "" ) { self .font = font self .color = color self .text = text } func clone () -> Paragraph { return Paragraph (font: self .font, color: self .color, text: self .text) } } let base = Paragraph ()let title = base.clone()title.font = UIFont .systemFont(ofSize: 18 ) title.text = "This is the title" let first = base.clone()first.text = "This is the first paragraph" let second = base.clone()second.text = "This is the second paragraph"
原型实现只需要很少的代码,包括一个默认的构造器和一个克隆方法。在 init
方法中,所有的东西都会被预先配置,你通过克隆方法拿到这个原型对象。
当你想要保留某个状态的快照 时,原型设计模式也有用处。比如,在一个绘图应用里,你用了一个形状作为原型,然后开始添加路径,画到某个节点时,你决定做一个快照,然后继续绘制。这个快照让你拥有在未来任意时刻回到那个节点的能力。