本文带有很浓重的主观色彩,有不同观点是非常正常的,欢迎指正。
设计模式是干什么的?
设计原则「道」
设计模式「术」
产品设计,就是尽可能让用户用得更舒服。不严谨的说,我们的OOP设计模式,就是尽可能让你写的代码(大部分情况是类Class)尽可能让你的「同事」包括自己用的舒服。
产品、外观、OOP对于好的设计有很多共同点,比如:简单,干净,不会绕来绕去。
设计模式中「模式」二字,代表类的设计是有「道」和「术」的规律可循的。
一句话:越简单越幸福。你起了一个类名LoginViewController,那你最好别做和Login无关的事情。
反例1:Register和Login的VC界面很像,只有部分不一样,这个时候比较糟糕的做法就:
class LoginViewController {
enum Type {
case login
case register
}
var type: Type = .login
func doSomething1() {
if type == .login {
/*.login 时的逻辑..*/
} else {
/*.register 时的逻辑..*/
}
}
func doSomething2() {
if type == .login {
/*.login 时的逻辑..*/
} else {
/*.register 时的逻辑..*/
}
}
}
LoginViewController 承担了注册和登录的职责,显然是不满足单一原则的。
显而易见的缺陷:
解决方案:
修改/新增一个功能,「尽可能」不用动到原来的代码,视情况而定。
图1小霸王游戏只要换卡就行了,图2俄罗斯方块换游戏的话要修改主板里的代码。。。显然小霸王更复合「开闭原则」
这个描述比较简略,如果我们详细表述一下,那就是,添加一个新的功能应该是,在已有代码基础上扩展代码(新增模块、类、方法等),而非修改已有代码(修改模块、类、方法等)。
举一个商城这边的实际例子:
1、上传订单信息到棒糖服务器,服务器下发一个pid。
2、调用支付宝sdk,并把pid传给支付宝sdk。
很容易得到这样的伪代码
class func Payment {
private var alipaySdk = AlipaySDK()
init() {
alipaySdk.delegate = self
}
func pay(with order: OrderInfo) {
let request = BMRequest(order: order)
request.start { pid
alipaySdk.pay(amount: order.amount,
pid: pid)
}
}
// 支付宝的回调用
func alipay(_ sdk, status: ...)
func alipay(_ sdk, userInfo: ...)
}
如果现在要加一个微信呢?你不得不在原来代码上做修改,pay方法很可能会变成这样。
class func Payment {
private var wechatSdk = WeChatSDK() // 😓新增
private var alipaySdk = AlipaySDK()
init() {
alipaySdk.delegate = self
wechatSdk.delegate = self
}
// ❌修改代码
func pay(with order: OrderInfo, type: PayType) {
let request = BMRequest(order: order)
request.start { pid
// ❌修改代码
if (type == .alipay) {
alipaySdk.pay(amount: order.amount,
pid: pid)
} else if (type == .wechat) {
wechatSdk.pay(amount: order.amount,
pid: pid)
}
}
}
// 支付宝的回调用
func alipay(_ sdk, status: ...)
func alipay(_ sdk, userInfo: ...)
// 😓新增代码
func wechat(_ sdk, status: ...)
func wechat(_ sdk, userInfo:...)
}
如果要加一个京东支付,我们还得继续在源代码上修改
我们的做法:
protocol PaySdk {
pay(amount: Int, pid: Int)
}
class Alipay: PaySdk {
func pay(amount: Int, pid: Int) {
...支付报相关的逻辑
}
// 支付宝的回调用
func alipay(_ sdk, status: ...)
func alipay(_ sdk, userInfo: ...)
}
class WeCheat: PaySdk {
func pay(amount: Int, pid: Int) {
...微信支付逻辑
}
// 微信回调用
func wechat(_ sdk, status: ...)
func wechat(_ sdk, userInfo:...)
}
class Payment {
private sdk: PaySdk!
init(sdk: PaySdk) {
self.sdk = sdk
}
func pay(with order: OrderInfo) {
let request = BMRequest(order: order)
request.start { pid
sdk.pay(amount: order.amount,
pid: pid)
}
}
// 使用
let alipay = Payment(sdk: Alipay())
alipay.pay(amount: ...)
let wechatPay = Payment(sdk: WeCheat())
wechatPay.pay(amount: ...)
之后如果要再来一个京东支付
你只要实现PaySdk 这个协议即可:
class JdPay: PaySdk {
func pay(amount: Int, pid: Int) {
...京东支付逻辑
}
// 京东的回调
func jd(_ sdk, status: ...)
func jd(_ sdk, userInfo:...)
}
// 使用
let jdpay = Payment(sdk: JdPay())
jdpay.pay(amount: ...)
新增功能写「新代码」,不需要改老代码,做到了开闭原则。
官方说法:为其他对象提供一种代理以控制对这个对象的访问。
想想我们的科学上网,就是代理模式.
假如我们访问YouTube,只发了一个POST请求,过程如下:
上行:客户端发起请求 —-> 代理服务器——> Youtube服务器
下行:Youtube服务器 —–> 代理服务器——> 客户端
下面还是来一个实际例子:
protocol User {
var isPrime: Bool { set get }
func syncToServer()
}
class FemometerUser: User {
var isPrime: Bool
init(_ isPrime: Bool) {
self.isPrime = isPrime
}
func syncToServer() {
// .. 服务器..
}
}
假设iOS没有 KVO功能,我们想监听isPrime属性的改变,但是又不想改动原来FemometerUser、User的代码,该怎么做呢?
protocol User {
var isPrime: Bool { set get }
func syncToServer()
}
class FemometerUser: User {
var isPrime: Bool
init(_ isPrime: Bool) {
self.isPrime = isPrime
}
func syncToServer() {
// .. 服务器..
}
}
// 代理
class UserIsPrimeObserverProxy: User {
private var target: User // 真实对象
public var isPrimeDidChanged: ((Bool) -> Void)?
var isPrime: Bool {
set {
// 转发给真实的对象, 可以加一些自己的逻辑
if newValue == target.isPrime {
// 去重
return
}
target.isPrime = newValue
isPrimeDidChanged?(newValue)
}
get {
// 从真实的对象中获取价值
return target.isPrime
}
}
func syncToServer() {
// 转发给真实的对象
target.syncToServer()
}
init(_ target: User) {
self.target = target
}
}
使用:
let user = FemometerUser(false)
let observerProxy = UserIsPrimeObserverProxy(user)
observerProxy.isPrimeDidChanged = { newValue in
print("change:\(newValue)")
}
// 使用代理访问
observerProxy.isPrime = false
observerProxy.isPrime = true
observerProxy.isPrime = true
observerProxy.isPrime = true
observerProxy.isPrime = false
// 使用代理访问同步服务器的方法
observerProxy.syncToServer()
// 输出
change:true
change:false
下面是KVO的官方实现原理图,运用了isa_hook实现对象级别的hook实现的,其本质还是代理模式。
运用共享技术有效地支持大量细粒度的对象。
享元(Flyweight)的核心思想很简单:如果一个对象实例一经创建就不可变,那么反复创建相同的实例就没有必要,直接向调用方返回一个共享的实例就行,这样即节省内存,又可以减少创建对象的过程,提高运行速度。
享元模式在Java标准库中有很多应用。我们知道,包装类型如Byte、Integer都是不变类,因此,反复创建同一个值相同的包装类型是没有必要的。以Integer为例,如果我们通过Integer.valueOf()这个静态工厂方法创建Integer实例,当传入的int范围在-128~+127之间时,会直接返回缓存的Integer实例:
public class Main {
public static void main(String[] args) throws InterruptedException {
Integer n1 = Integer.valueOf(100);
Integer n2 = Integer.valueOf(100);
System.out.println(n1 == n2); // true 说明地址相同
}
}
想想我们的UITableView的复用机制,是不是用的享元模式?
官方说法:迭代器模式是一种行为设计模式,让你能在不暴露集合底层表现形式 (列表、 栈和树等) 的情况下遍历集合中所有的元素。
还是来一个fm实际例子,我们想实现一个这样的开发体验
let body1 = BodyStatus(type: .bbt, id: 1)
let body2 = BodyStatus(type: .period, id: 2)
let body3 = BodyStatus(type: .bbt, id: 3)
let body4 = BodyStatus(type: .bbt, id: 4)
let body5 = BodyStatus(type: .period, id: 5)
let body6 = BodyStatus(type: .bbt, id: 6)
let seq = PeroidSequence(origin: [body1,
body2,
body3,
body4,
body5,
body6])
for i in seq {
print(i.id)
}
// 输出结果 为 2 和 5
下面是实现方法
struct PeroidSequence: Sequence {
let origin: [BodyStatus]
func makeIterator() -> PeriodIterator {
return PeriodIterator(self)
}
}
struct PeriodIterator: IteratorProtocol {
typealias Element = BodyStatus
private var index = 0
private let seq: PeroidSequence
init(_ seq: PeroidSequence) {
self.seq = seq
}
mutating func next() -> BodyStatus? {
let count = seq.origin.count
while (index < count && seq.origin[index].type != .period) {
index += 1
}
if index >= count {
return nil
}
let result = seq.origin[index]
index += 1
return result
}
}
可以看到,我们我们把「访问type为period的bodystatus的逻辑」封装起来了,下次使用只要用「更加优雅」for ..in 就可以了,不需要每次在for循环里做判断。
此外,我们还可以为类似二叉树这样的非线性结构创建不同的迭代器,例如:创建一个「先序遍历迭代器」和 「层次遍历迭代器」,原理同上就不写具体代码了。
同事就是你的用户,互相给一些feedback吧。