学计算机的那个

不是我觉到、悟到,你给不了我,给了也拿不住;只有我觉到、悟到,才有可能做到,能做到的才是我的.

0%

Swift花园之设计模式 下

静态工厂

静态工厂模式的三个好处

命名构造器

静态工厂模式的头等好处是:每一个静态工厂方法都有一个自己的名字。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.week604_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

// MARK: - 缓存

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]!
}

// MARK: - 静态工厂

static var local: Service {
return Service.cached(name: "local")
}

static var remote: Service {
return Service.cached(name: "remote")
}

// MARK: - 初始化

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")
}
}

注意,你可以利用 finalstatic 关键字来限制继承。如果要开放继承,你需要移除 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.primary
let 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()

抽象工厂

  • 通过工厂方法组合简单工厂
  • 对整个app有全局影响
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 Foundation

class 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 方法中,所有的东西都会被预先配置,你通过克隆方法拿到这个原型对象。

当你想要保留某个状态的快照时,原型设计模式也有用处。比如,在一个绘图应用里,你用了一个形状作为原型,然后开始添加路径,画到某个节点时,你决定做一个快照,然后继续绘制。这个快照让你拥有在未来任意时刻回到那个节点的能力。