独孤九剑--设计模式(iOS创建型篇)
独孤九剑--设计模式(iOS行为型篇)
适配器模式 Adapter Pattern
情景
Apple公司维护了其员工管理系统,员工数据如下:
struct AppleEmployee {
var name: String
var seniority: Int
var salary: Double
func getEmployeeInfo() -> String {
return "员工:\(name), 工龄:\(seniority), 薪酬:\(salary)"
}
}
现在Apple公司收购了Spaces公司,Spaces公司也有一套自己的员工管理系统,员工数据如下;显而易见的是,不管从数据结构,还是提供的功能来说,两者完全不兼容;
struct SpacesEmployee {
var s_name: String
var s_seniority: Int
var s_salary: Int
}
现在Apple需要将Spaces员工接入到自己的系统;
简单粗暴的做法有2种:
-
直接修改Spaces员工系统所有源码,重构成Apple系统要求的数据结构、方法调用;
对于庞大的系统而言,这种改法工作量巨大而且就容易出错; -
在使用Apple员工功能的地方,都加一个对应的Spaces功能;
以系统中的一个获取员工信息的功能为例
// 原有系统的功能
func dealEmployee(employee: AppleEmployee) {
// ...
print(employee.getEmployeeInfo())
}
Spaces员工也需要支持该功能,重写一样的功能:
func dealEmployee(employee: SpacesEmployee) {
// ...
let spacesEeInfo = "员工:\(employee.s_name), 工龄:\(employee.s_seniority), 薪酬:\(Double(employee.s_salary))"
print(spacesEeInfo)
}
这种方式相比第一种方案工作量小一点点,但代码同样很难维护;
(这还是基于Swift语言的方法重载而言,如果是OC那将会比这个更复杂,不能使用重载那OC就需要判断到底是哪个类型,增加一系列if else)
破解招式
适配器模式
将一个接口转换成客户希望的另一个接口,从而使原本接口不匹配而无法一起工作的两个类能够在一起工作。
适配器模式又分为2种:类适配器模式,对象的适配器模式
类适配器
类适配器模式是声明一个公共接口,通过继承的方式把适配的类的API转换成为目标类的API。
UML类图
class adapterTarget:目标抽象类;Adapter:适配器类;Adaptee:适配者类
代码实现
- 提取adapter、adaptee需要用到的公共属性、方法,封装成接口/protocol
protocol Employee {
var salary: Double { get }
func getEmployeeInfo() -> String
}
- 扩展target (AppleEmployee),实现Employee协议
// target
extension AppleEmployee : Employee {
// Employee协议接口都是根据AppleEmployee 提取的; 因此原本就已经实现协议方法了
}
- 新建adapter类,继承需要适配的adaptee并实现Employee协议;(如果是C++这种支持多继承的语言,adapter可以继承adaptee和target)
class SpacesEmployeeClassAdapter : SpacesEmployee, Employee {
var salary: Double {
return Double(self.salary)
}
func getEmployeeInfo() -> String {
return "员工:\(self.s_name), 工龄:\(self.s_seniority), 薪酬:\(Double(self.s_salary))"
}
}
- 重构代码,AppleEmployee都替换成Employee;需要接入SpacesEmployee的使用其子类SpacesEmployeeClassAdapter
let appleEmployee = AppleEmployee(name: "jobs", seniority: 20, salary: 100000)
dealEmployee(employee: appleEmployee)
let employeeAdapter = SpacesEmployeeClassAdapter(s_name: "jack", s_seniority: 1, s_salary: 5000)
dealEmployee(employee: employeeAdapter)
func dealEmployee(employee: Employee) {
// ...
print(employee.getEmployeeInfo())
}
对象适配器
与类适配器不同,对象适配器不继承被适配者,而是组合了一个对其引用。
UML类图
类适配器中,由于语言不支持多继承,创建了公有协议,变相的也更改了使用Target的具体类(AppleEmployee)的地方;对象适配器,适配器不需要继承被适配者,现在就可以将适配器对象直接继承自Target的具体类,公有协议也无需创建;
instance adapter代码实现
- 新建adapter对象SpacesEmployeeInstanceAdapter, 继承自AppleEmployee;adapter内引用adaptee对象,重写AppleEmployee方法;
class SpacesEmployeeInstanceAdapter : AppleEmployee {
var spacesEmployee: SpacesEmployee
init(spacesEmployee: SpacesEmployee) {
self.spacesEmployee = spacesEmployee
super.init(name: spacesEmployee.s_name, seniority: spacesEmployee.s_seniority, salary: Double(spacesEmployee.s_salary))
}
override func getEmployeeInfo() -> String {
return "员工:\(spacesEmployee.s_name), 工龄:\(spacesEmployee.s_seniority), 薪酬:\(self.salary)"
}
}
- 需要接入SpacesEmployee的使用SpacesEmployeeInstanceAdapter
let appleEmployee = AppleEmployee(name: "jobs", seniority: 20, salary: 100000)
dealEmployee(employee: appleEmployee)
let spacesEmployee = SpacesEmployee(s_name: "jack", s_seniority: 1, s_salary: 5000)
let employeeAdapter = SpacesEmployeeInstanceAdapter(spacesEmployee: spacesEmployee)
dealEmployee(employee: employeeAdapter)
func dealEmployee(employee: AppleEmployee) {
// ...
print(employee.getEmployeeInfo())
}
对象适配器和类适配器对比
适配器模式使用场景
适配器模式属于是被动设计的,使用适配器模式一般都是发现系统现有类的接口不符合系统的需要,或者多套相同功能系统间不兼容,需要更优雅实现而引入;如果在最开始就已经知道后续的需求,那完全可以在当时就设计好接口、数据结构等;
另一个场景是,使用不同三方库的相同功能时,当无法修改他们的源码,适配器模式也会是一个很好的处理方式
适配器模式在iOS系统中的使用
iOS中的委托(delegate、dataSource)按其用途来说其实是使用了对象适配器模式的;实现代理协议的具体类就是个适配器;
以UITableView为例,tableView需要多少行的代码:
tableView. dataSource=vc;
// vc
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return self.datas.count;
}
vc就是adapter,它实现了tableView的numberOfRowsInSection
接口要求,适配了tableView;datas就相当于adaptee,vcnumberOfRowsInSection
的具体实现中,调用了datas而得到真正需要的数据;
桥接模式 Bridge
情景
App需要新增一个分享功能;
- 第一个版本需求是分享文字到v信;
简单的面向对象代码:
// v信分享平台
struct WXSharePlatform {
func shareText(text: String) {
// WXApi ....
// ...
print("\(text) v信分享成功")
}
}
// 文字分享类
struct WXTextShare {
var text: String
private let wxPlatform = WXSharePlatform()
func share() {
wxPlatform.shareText(text: text)
}
}
客户端分享调用:
let share = WXTextShare(text: "心灵毒鸡汤...")
share.share()
实现的还行。。。
- 迭代需求:需要支持对图片内容的分享,同时分享平台还要支持QQ;
还是一样的套路,新建对应的类即可:
// QQ平台
struct QQSharePlatform {
func shareText(text: String) {
// QQApi ....
print("\(text) QQ分享成功")
}
func shareImage(imageData: Data) {
// QQApi ....
print("图片 QQ分享成功")
}
}
// 新增wx图片分享、新增qq文字分享、新增qq图片分享 处理类
struct WXImageShare {
....
}
struct QQTextShare {
...
}
struct QQImageShare {
...
}
忍忍还是能勉强接受。。。
- 迭代需求,需要支持对视频内容的分享
。。。 - 迭代需求,需要支持微博平台
。。。
我想你已经疯了,也意识到问题所在:
随着功能不断增加,类层级爆炸式增长,而且这些类大部分代码都是重复的;
破解招式
桥接模式
将抽象部分与它的实现部分分离,使它们都可以独立地变化
以上示例,存在两个维度:1. 分享内容,2.分享平台;
使用桥接模式需要做的,就是将分享内容分享平台分离,使他们独立变化;
UML类图
bridge- Abstraction:定义抽象接口,拥有一个Implementor类型的对象引用
- RefinedAbstraction:扩展Abstraction中的接口定义
- Implementor:具体实现的接口
- ConcreteImplementor:实现Implementor接口,给出具体实现
代码实现
- 抽象出分享平台实现类接口
// implementor
protocol SharePlatform {
func shareText(text: String)
func shareImage(imageData: Data)
}
- 重构分享平台实现类
// ConcreteImplementor
// 内部实现不变
struct WXSharePlatform : SharePlatform {
func shareText(text: String) {
...
}
func shareImage(imageData: Data) {
...
}
}
struct QQSharePlatform : SharePlatform {
func shareText(text: String) {
...
}
func shareImage(imageData: Data) {
...
}
}
- 定义分享内容的抽象接口
// Abstraction
protocol ShareContent {
var implementor: SharePlatform { get set }
func share()
}
- 扩展分享内容接口定义
// RefinedAbstraction
struct TextShare : ShareContent {
var implementor: SharePlatform
var text: String
func share() {
implementor.shareText(text: text)
}
}
struct ImageShare : ShareContent {
var implementor: SharePlatform
var imageData: Data
func share() {
implementor.shareImage(imageData: imageData)
}
}
客户端调用分享:
let wx = WXSharePlatform()
let share1 = TextShare(implementor: wx, text: "心灵毒鸡汤...")
share1.share()
let imgData = ...
let qq = QQSharePlatform()
let share2 = ImageShare(implementor: qq, imageData: imgData)
share2.share()
当需要增加一类分享内容,或新增一类分享平台,都只需要增加一个类即可;具体的操作都通过Abstraction与Implementor组合实现
适用场景
- 扩展相同功能时,不断的新增类导致的类层级爆炸问题;可以采用桥接模式
- 如果出现抽象部分和实现部分都应该可以扩展的情况,可以采用桥接模式
- 如果你不希望在抽象和实现部分采用固定的绑定关系,可以采用桥接模式
装饰模式 Decorator Pattern
情景
开发一套网上商城功能:售卖指定品类的物品,每类物品价格固定;选择商品后给出商品信息及其价格;
简单实现:
抽象商品公有接口
protocol Goods {
var name: String { get }
var price: Double { get }
}
extension Goods {
func sold() {
print("\(name), price:\(price)")
}
}
为每种商品新建对应对象
class Flower: Goods {
var name: String = "鲜花"
var price: Double = 99.9
}
class Chocolate: Goods {
var name: String = "巧克力"
var price: Double = 66
}
使用
let flower = Flower()
flower.sold()
// 鲜花, price:99.9
需求迭代:适逢节日,商城为每种商品提供有偿的礼物包装和贺卡销售,需要编码支持;
简单。。。在原有商品基础上继承一个新类即可:
// 商品= 鲜花+礼物包装
class FlowerWithGift: Flower {
private let giftPrice = 5.0
override init() {
super.init()
name = super.name + "+" + "礼物包装"
price = super.price + giftPrice
}
}
class FlowerWithCard: Flower {...}
class ChocolateWithGift: Chocolate {...}
class ChocolateWithCard: Chocolate {...}
选择了鲜花并且需要包装的:
let flowerGift = FlowerWithGift()
flowerGift.sold()
// 鲜花+礼物包装, price:104.9
如果消费者选择鲜花并且需要包装的,同时还要求提供贺卡呢?
好像写的类还不满足,那就再加一个:
class FlowerWithGiftCard: FlowerWithGift {...}
如果再增加一个商品附属品呢,按照组合计算,那每个商品类就都需要2^3-1=7个子类了。。。
破解招式
装饰模式
动态地给一个对象添加一些额外的职责。就扩展功能来说,装饰模式相比生成子类更为灵活
UML类图
decorator.png- Component:给出一个抽象接口,以规范准备接受附加责任的对象。
- ConcreteComponent:定义一个将要接收附加责任的类。
- Decorator:持有一个构件(Component)对象的实例,并定义一个与抽象构件接口一致的接口。
- ConcreteDecorator:负责给构件对象添加附加职责。
代码实现
- 抽象商品附属品协议,继承自原有商品协议
// Decorator
// 商品附属品
protocol GoodsAttached: Goods {
var goods: Goods { get }
}
- 礼物包装、贺卡单独封装 具体实现
// concreteDecorator
// 礼物包装
struct Gift: GoodsAttached {
private let giftPrice = 5.0
var goods: Goods
var name: String {
return goods.name + "+" + "礼物包装"
}
var price: Double {
return goods.price + giftPrice
}
}
// 贺卡
struct Card: GoodsAttached {
private let cardPrice = 1.1
var goods: Goods
var name: String {
return goods.name + "+" + "贺卡"
}
var price: Double {
return goods.price + cardPrice
}
}
- 原有商品类Component、ConcreteComponent不做更改;
- 商品+礼物包装+贺卡 调用
let flower = Flower()
let giftWrap = Gift(goods: flower)
let cardWrap = Card(goods: giftWrap)
cardWrap.sold()
// 鲜花+礼物包装+贺卡, price:106.0
后续再增加附属品时,只要增加一个对应的concreteDecorator类即可
适用场景
- 用继承扩展功能不太现实的情况下,应该考虑用组合的方式
- 透明并且动态地给对象增加新的职责
外观模式 Facade Pattern
为子系统中的一组接口提供一个一致的界面,Facade模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。
相对比较简单
facade.png// facade
struct System {
let sa = SystemA()
let sb = SystemB()
let sc = SystemC()
func start() {
// 封装调用子系统
sa.open()
sb.turnOn()
sc.run()
}
}
代理模式 Proxy Pattern
为其他对象提供一种代理,以控制对这个对象的访问
UML类图
proxy.png- Subject:抽象主题角色,声明了目标对象和代理对象的共同接口,这样一来在任何可以使用目标对象的地方都可以使用代理对象。
- RealSubject:具体主题角色,被代理对象,定义了代理对象所代表的目标对象。
- Proxy:代理类,代理对象内部含有目标对象的引用,从而可以在任何时候操作目标对象;代理对象提供一个与目标对象相同的接口,以便可以在任何时候替代目标对象。代理对象通常在客户端调用传递给目标对象之前或之后,执行某个操作,而不是单纯地将调用传递给目标对象。
示例
新版mac只有美丽国才能买到,在中国需要购买就需要通过代购(代理Proxy);
- 创建购买者抽象对象接口
protocol Buyer {
func buyMac()
}
- 创建真实的购买者对象
struct ChinaBuyer: Buyer {
func buyMac() {
print("china buyer buy Mac")
}
}
- 创建代理对象
struct BuyerProxy: Buyer {
let buyer = ChinaBuyer()
func buyMac() {
buyer.buyMac()
print("from usa")
}
}
调用:
let proxy = BuyerProxy()
proxy.buyMac()
应用
代理模式的应用形式:
- 远程代理:即为一个对象在不同的地址空间提供局部代表,这样可以隐藏一个对象存在于不同地址空间的事实;
- 虚拟代理:即根据需要创建开销很大的对象,通过它来存放实例化需要很长时间的真实对象;
- 安全代理:用来控制真实对象访问时的权限;
- 智能指引:即当调用真实对象时,代理处理另外一些事。
iOS系统中的代理模式
说到代理,iOS开发立马就想到delegate,dataSource;
但实际上,他们并不是代理模式,只能称为委托,实质上还是归为适配器模式(参考上面适配器模式);
代理模式和对象适配器模式,从UML图也可以看出,他们是及其类似的;有区别的是代理模式中RealSubject也需要实现同一个接口,而适配器中Adaptee无限制;根据实际应用场景也很容易分辨出来;
而iOS中真正使用到代理模式的NSProxy,NSProxy是个抽象基类,使用时需子类化;
具体可以根据YYKit的YYWeakProxy分析:
@interface YYWeakProxy : NSProxy
/**
The proxy target.
*/
@property (nullable, nonatomic, weak, readonly) id target;
// return A new proxy object.
- (instancetype)initWithTarget:(id)target;
...
@end
YYWeakProxy对应proxy,target就是RealSubject;
proxy重写了forwardingTargetForSelector
等动态转发方法,当给proxy发送消息,基于oc runtime机制会将消息转发给target;实现了代理作用;
(YYWeakProxy这种代理模式,主要是为了解决内存循环引用问题)
参考:
《Objective-C编程之道》
《精通Swift设计模式》
《大话设计模式》
网友评论