结构型设计模式
结构型设计模式主要用于处理类和对象的组合
适配器模式
- 将一个原始接口转成客户端需要的接口
- 原始接口--相当于Adaptee
- 客户端需要接口(调用者) --- 相当于Target
- 原始接口不兼容现在新的接口,让它们两个可以一起工作
- 一起工作需要Adapter实现
角色分析:
- 适配器
- 目标接口
- 被适配者
例如,在我们常见UITableView代码中:
适配器是ViewController(实现两个协议)
目标接口是UI界面->UITableView(Cell)
被适配者是数据(Model)
这种时候,UIViewController中的代码会非常臃肿。这时候,我们可以创建一个Adapter类来做适配器,将tableView的dataSource和delegate赋值给Adapter对象,在Adapter中实现协议,完成数据的交互。 这样,我们就将ViewController和Model以及View都进行分离。而且,如果我们要在ViewController中关心cell的点击事件,也可以通过适配器传递。
适配器分为类适配器和对象适配器。
类适配器
类适配器中适配器继承于被适配者,适配器方法中调用父类的方法进行适配
如下例中,适配器Adapter将adaptee中的美元适配为人民币
//被适配者
class Adpatee{
var dollar: Double{
1000.0
}
}
//目标接口
protocol Target{
var RMB: Double{ get }
}
//适配器
class Adapter: Adpatee, Target{
var RMB: Double{
dollar * 7.0
}
}
对象适配器
对象适配器中,Adapter遵守了Target协议,拥有一个adaptee的引用,当调用Adpater的适配方法时,内部实现对adaptee的方法进行适配。
如下例中,适配器Adapter将adaptee中的美元适配为人民币
//被适配者
class Adpatee{
var dollar: Double{
return 1000.0
}
}
//目标接口
protocol Target{
var RMB: Double{ get }
}
//适配器
class Adapter: Target{
init(_ adaptee: Adpatee){
self.adaptee = adaptee
}
var adaptee: Adpatee
var RMB: Double{
adaptee.dollar * 7.0
}
}
桥接模式
- 定义
将抽象部分与实现部分分离,使它们都可以独立的变化。 - 解决问题:
类层级爆炸问题 - 使用场景:
- 开发中面临层级结构复杂(爆炸),可以使用桥接模式
- 对不同的API之间进行桥接
- 一个类存在两个独立的维度,且这两个维度都需要进行扩张(至少是两个维度)
- 角色划分
- 抽象部分
- 实现抽象部分
- 具体部分
- 具体实现部分
下面是一个简单的例子,程序员作为抽象部分,程序员的工作内容为具体抽象部分,iOS、安卓、java程序员为具体部分,iOS、安卓、java工作内容为具体实现部分。
//程序员,抽象部分
protocol ComputerProgrammer{
init(_ op: WorkOperation)
var op: WorkOperation{ get }
func work()
}
//具体工作(做什么),实现抽象部分
protocol WorkOperation{
func operation()
}
//MARK: 具体抽象部分
class iosProgrammer: ComputerProgrammer{
required init(_ op: WorkOperation) {
self.op = op
}
var op: WorkOperation
func work() {
print("ios工程师开始工作")
op.operation()
}
}
class androidProgrammer: ComputerProgrammer {
required init(_ op: WorkOperation) {
self.op = op
}
var op: WorkOperation
func work() {
print("安卓工程师开始工作")
op.operation()
}
}
class javaProgrammer: ComputerProgrammer {
required init(_ op: WorkOperation) {
self.op = op
}
var op: WorkOperation
func work() {
print("Java工程师开始工作")
op.operation()
}
}
//MARK: 具体实现部分
class iosOperation: WorkOperation{
func operation() {
print("使用OC或Swift开发")
}
}
class androidOperation: WorkOperation{
func operation() {
print("使用java或kotlin开发")
}
}
class javaOperation: WorkOperation{
func operation() {
print("使用java开发")
}
}
let ios = iosProgrammer(iosOperation())
ios.work()
let android = androidProgrammer(androidOperation())
android.work()
let java = javaProgrammer(javaOperation())
java.work()
打印结果:
ios工程师开始工作
使用OC或Swift开发
安卓工程师开始工作
使用java或kotlin开发
Java工程师开始工作
使用java开发
组合模式(Composite)
将对象组合成树形结构以表示部分-整体
的层次结构,组合模式使得用户对单个对象和组合对象的使用具有一致性,用户可以统一的使用组合结构中的所有对象。
总结:使得用户对单个对象和组合最新的使用具有一致性。
最简单的最常见的例子就是UIView,典型的树形结构。
角色分析:
- 抽象根节点(Component)
- 具体子节点(Composite)
- 叶子结点(Leaf)
Composite、Leaf指一类结点,并不是唯一,其中Leaf是无子结点(叶子结点)。
使用场景:
只允许使用,不允许继承。
组合模式最大的优点是它的结点可以自由增加,且调用结点方便。
//Component
@objc protocol TreeProtocol{
var trees: Array<TreeProtocol> { get }
func doSomething()
@objc optional func addChild(_ child: TreeProtocol)
@objc optional func removeChild(_ index: Int)
@objc optional func getChildren(_ index: Int) -> TreeProtocol
@objc optional func clear()
}
//Composite
class Tree: TreeProtocol{
var trees: Array<TreeProtocol>
init(){
trees = Array<TreeProtocol>()
}
func doSomething() {
}
func addChild(_ child: TreeProtocol) {
trees.append(child)
}
func removeChild(_ index: Int) {
trees.remove(at: index)
}
func getChildren(_ index: Int) -> TreeProtocol {
trees[index]
}
func clear() {
trees.removeAll()
}
}
//Leaf
class Leaf: TreeProtocol{
var trees: Array<TreeProtocol>
init(){
trees = Array<TreeProtocol>()
}
func doSomething() {
}
}
装饰模式(Decorator)
- 定义
动态地给一个对象添加一些额外的职能 - 场景
需要透明且动态地扩展类的功能时 - 角色分析
- 抽象组件
- 具体组件
- 抽象装饰者
- 具体装饰者
下面的案例中,
- 抽象组件是手机
- 具体组件是iPhone11
- 抽象装饰者是手机壳
- 具体装饰者
- 质量好的手机壳:耐磨、防水、防尘...(300)
- 耐磨:wearProof()
- 防水:waterProof()
- 防尘:dustProof()
GoodShell
- 质量差的手机壳:耐磨(50)
- 耐磨:wearProof()
PoorShell
- 耐磨:wearProof()
- 质量好的手机壳:耐磨、防水、防尘...(300)
//抽象组件:手机
protocol MobilePhone{
//装饰->手机壳
func shell()
}
//具体组件:iPhone11
class Iphone11: MobilePhone {
func shell() {
print("苹果11")
}
}
//抽象装饰者
class MobilePhoneShell: MobilePhone{
private var mobile: MobilePhone
init(_ mobile: MobilePhone) {
self.mobile = mobile
}
func shell() {
self.mobile.shell()
}
}
//具体装饰者->好的手机壳
class GoodShell: MobilePhoneShell{
func wearProof(){
print("耐磨功能")
}
func waterProof(){
print("防水功能")
}
func dustProof(){
print("防尘功能")
}
}
//具体装饰者->差的手机壳
class PoorShell: MobilePhoneShell{
func wearProof(){
print("耐磨功能")
}
}
let phone = Iphone11()
phone.shell()
let good = GoodShell(phone)
good.shell()
good.dustProof()
good.waterProof()
good.wearProof()
let poor = PoorShell(phone)
poor.shell()
poor.wearProof()
//打印结果
苹果11
苹果11
防尘功能
防水功能
耐磨功能
苹果11
耐磨功能
动态地给一个对象添加一个额外的只能,就增加功能来说,Decorator模式相比生成子类更为灵活。
在OC中,我们可以通过Category来实现装饰模式。虽然通过Category可以实现装饰模式,但是这并不是一个严格的实现,由类别添加的方法是编译时绑定的,而装饰模式是动态绑定的,另外类别也没有封装被扩展类的实例。类别适合装饰器不多的时候。
外观模式(Facade)
- 定义:门面模式
- 要求一个子系统的外部和其内部的通信必须通过统一的对象进行
- 外观模式提供了一个高层次接口,使得子系统更易于使用
- 场景
- 场景一:为复杂系统提供一个简单接口
- 场景二:当我们构建一个层次结构的子系统时,使用Facede模式定义子系统中每一层入口点。如果子系统之间相互依赖,可以通过此模式简化它们之间的依赖关系。
- 角色分析
- 系统对外统一接口
- 子系统接口
系统类使用UIImagePickerController为拍照提供了高层次接口,而内部是使用AVFoundation封装的,这种都是外观模式的体现。
下面案例我们使用手机来举例,手机提供了拍照、玩游戏、听音乐等高层次接口,而其内部都是调用各自的子系统来实现的。
class Iphone {
private var camera: CameraProtocol?
private var game: GameProtocol?
private var music: MusicProtocol?
init(camera: CameraProtocol? = nil,game: GameProtocol? = nil,music: MusicProtocol? = nil) {
self.camera = camera
self.game = game
self.music = music
}
//相机
func cameraAction(){
//启动相机,开始拍照,关闭相机
self.camera?.open()
self.camera?.takePicture()
self.camera?.close()
}
//玩游戏
func palyGame(){
//启动游戏,开始游戏,关闭游戏
self.game?.open()
self.game?.startGame()
self.game?.close()
}
//听音乐
func palyMusic(){
//启动音乐,开始听歌,关闭音乐
self.music?.open()
self.music?.startMusic()
self.music?.close()
}
}
//相机模块接口
protocol CameraProtocol{
func open()
func takePicture()
func close()
}
//具体实现类
class CameraImpl: CameraProtocol{
func open() {
print("打开相机")
}
func takePicture() {
print("拍照")
}
func close() {
print("关闭相机")
}
}
//游戏模块接口
protocol GameProtocol{
func open()
func startGame()
func close()
}
//游戏具体实现类
class GameImpl: GameProtocol{
func open() {
print("启动游戏")
}
func startGame() {
print("开始游戏")
}
func close() {
print("关闭游戏")
}
}
//音乐模块接口
protocol MusicProtocol{
func open()
func startMusic()
func close()
}
//具体音乐实现
class MusicImpl: MusicProtocol {
func open() {
print("打开音乐")
}
func startMusic() {
print("播放音乐")
}
func close() {
print("关闭播放")
}
}
享元模式(FlyWeight)
- 定义
使用共享对象可以有效的支持大量的细粒度对象,强调:对象共享 - 场景
- 场景一:系统存在大量的相似对象
- 场景二:需要缓冲池场景
- 角色分析
- 享元对象接口
- 具体享元对象
- 享元工厂(负责创建对象、管理对象)
例子:我们在一个界面上生成1000个花,花的种类一共六种(六种不同图片),如图所示
我们不用享元模式代码如下:
enum FlowerType: Int{
case anemone,cosmos,gerberas,hollyhock,jasmine,zinnia,totalNumbersOfFlowerTypes
}
class ViewController: UIViewController{
override func viewDidLoad() {
super.viewDidLoad()
for _ in 0 ..< 1000{
autoreleasepool {
let screenBounds = view.bounds
let x = arc4random()%UInt32(screenBounds.width)
let y = arc4random()%UInt32(screenBounds.height)
let minSize = 10
let maxSize = 50
let WH = (Int(arc4random()) % (maxSize - minSize + 1)) + minSize
let area = CGRect(x: Int(x), y: Int(y), width: WH, height: WH)
let type = Int(arc4random()) % FlowerType.totalNumbersOfFlowerTypes.rawValue
let imageView = flowerView(FlowerType(rawValue: type) ?? FlowerType.anemone)
imageView.frame = area
view.addSubview(imageView)
}
}
}
//根据类型获取
func flowerView(_ type: FlowerType) -> UIImageView{
var imageName = ""
switch type {
case .anemone:
imageName = "anemone.png"
case .cosmos:
imageName = "cosmos.png"
case .gerberas:
imageName = "gerberas.png"
case .hollyhock:
imageName = "hollyhock.png"
case .jasmine:
imageName = "jasmine.png"
case .zinnia:
imageName = "zinnia.png"
default:
break
}
let image = UIImage(named: imageName)
return UIImageView(image: image)
}
}
如上面的代码所示,我们一共创建了1000个视图添加到view上,这样做的后果就是生成大量的对象,内存占用很大,特别图片如果也很大的话,进行绘制会耗费太多的GPU渲染。
从代码上看,其实只有6种花放在不同的位置而已,那我们可以利用享元模式思想,复用这两种花,然后绘制到不同位置,而不是增加对象添加到视图上。
enum FlowerType: Int{
case anemone,cosmos,gerberas,hollyhock,jasmine,zinnia,totalNumbersOfFlowerTypes
}
//具体享元对象
class FlowerView: UIImageView{
override func draw(_ rect: CGRect) {
image?.draw(in: rect)
}
}
//享元工厂
class FlowerFactory {
//缓存池
var flowerPool = [FlowerType : UIImageView]()
//缓存池中获取,如果缓存池中没有,则创建一个
func flowerView(_ type: FlowerType) -> UIImageView{
var imageView = flowerPool[type]
if imageView == nil {
var imageName = ""
switch type {
case .anemone:
imageName = "anemone.png"
case .cosmos:
imageName = "cosmos.png"
case .gerberas:
imageName = "gerberas.png"
case .hollyhock:
imageName = "hollyhock.png"
case .jasmine:
imageName = "jasmine.png"
case .zinnia:
imageName = "zinnia.png"
default:
break
}
let image = UIImage(named: imageName)
imageView = FlowerView(image: image)
flowerPool[type] = imageView!
}
return imageView!
}
}
class FlyWeightView: UIView{
var flowerList: Array<[NSValue : UIImageView]>?
override func draw(_ rect: CGRect) {
guard let flowerList = flowerList else {
return
}
for dic in flowerList {
guard let key = dic.keys.first,let imageView = dic.values.first else {
continue
}
let area = key.cgRectValue
imageView.draw(area)
}
}
}
class ViewController: UIViewController{
override func viewDidLoad() {
super.viewDidLoad()
//使用享元模式
let factory = FlowerFactory()
var flowerList = Array<[NSValue:UIImageView]>()
for _ in 0 ..< 1000{
autoreleasepool {
let type = Int(arc4random()) % FlowerType.totalNumbersOfFlowerTypes.rawValue
//重复利用对象
let flowerView = factory.flowerView(FlowerType(rawValue: type) ?? FlowerType.anemone)
let screenBounds = view.bounds
let x = arc4random()%UInt32(screenBounds.width)
let y = arc4random()%UInt32(screenBounds.height)
let minSize = 10
let maxSize = 50
let WH = (Int(arc4random()) % (maxSize - minSize + 1)) + minSize
let area = CGRect(x: Int(x), y: Int(y), width: WH, height: WH)
let key = NSValue(cgRect: area)
let dictionary = [key : flowerView]
flowerList.append(dictionary)
}
}
let flyWeightView = FlyWeightView(frame: view.bounds)
flyWeightView.flowerList = flowerList
self.view = flyWeightView
}
}
可以明显看到内存已经降下来了,我们只是生成了对象flowerView,但是并没有add到FlyweightView上,我们使用image重新绘制了一个新的位置去显示。
tableViewCell的重用机制就是实现了享元模式,在要使用一个cell的时候,会先去重用池中查询是否有可重用的cell,如果有则重用,没有就创建一个,这就是享元模式。
享元模式最主要有两个关键组件,可共享的享元对象和保存它们的享元池
代理模式
- 定义
为其它对象提供一种代理,控制对这个对象的访问。 - 角色分析
- 代理对象
- 持有目标对象引用
- 实现目标接口
- 目标接口
- 具体目标对象
- 代理对象
- 变种:动态代理(Java)、静态代理
比如我们让代购的人帮忙购买手机
//目标接口,代购iPhone
protocol PersonProtocol{
//下单(选购)
func buyProduct()
//支付
func payProduct()
}
//代理对象
class Person{
//代理
var proxy: PersonProtocol?
func buy(){
proxy?.buyProduct()
}
func pay(){
proxy?.payProduct()
}
}
//具体目标对象
class Proxy: PersonProtocol{
func buyProduct() {
}
func payProduct() {
}
}
网友评论