[TOC]
创建型模式
社会化的分工越来越细,自然在软件设计方面也是如此,因此对象的创建和对象的使用分开也就成为了必然趋势。因为对象的创建会消耗掉系统的很多资源,所以单独对对象的创建进行研究,从而能够高效地创建对象就是创建型模式要探讨的问题
对象模式
将数据与操作数据的逻辑放在一起,即封装
原型模式
深拷贝 浅拷贝 深拷贝复制地址 浅拷贝复制指针
单例模式
整个软件生命周期只有一个对象
实现方式:
- 饿汉模式(线程安全,调用效率高,但是不能延时加载)
- 懒汉模式(线程不安全,调用效率不高,但是能延时加载)懒加载(解决这个问题可以使用在方法上加synchronized的方式来解决,但这种方式每次调用时都使用阻塞方法效率较低,可以使用双重检查锁改进)
- 双重检查锁模式
- 静态内部类式(线程安全,调用效率高,可以延时加载)Swift貌似没有
- 枚举类(线程安全,调用效率高,不能延时加载,可以天然的防止反射和反序列化调用)Swift貌似没有
单例模式优先选用饿汉模式,如果需要使用懒加载最好使用静态内部类的方式实现,或者使用双重检查锁查的方式
优点:
- 在程序任何地方可以直接访问或者封装共享资源
- 由于在系统内存中只存在一个对象,因此可以节约系统资源,对于一些需要频繁创建和销毁的对象单例模式无疑可以提高系统的性能。
- 提供了对唯一实例的受控访问
缺点:
- 单例类的职责过重,在一定程度上违背了“单一职责原则”
- 由于单利模式中没有抽象层,因此单例类的扩展有很大的困难
- 滥用单例将带来一些负面问题,如为了节省资源将数据库连接池对象设计为的单例类,可能会导致共享连接池对象的程序过多而出现连接池溢出;如果实例化的对象长时间不被利用,系统会认为是垃圾而被回收,这将导致对象状态的丢失
注意:
- 高并发访问时需要实现保护机制
- 在单例设计模式中,一般情况下是不能被继承的,因为它的构造函数被私有化,通过提供其他的构造函数就能够在子类中显示的调用并完成初始化,不过这样一来,这个按照单例设计模式设计的类就偏离了它最初设计的目的类
适用场景:
- 需要频繁实例化然后销毁的对象。
- 创建对象时耗时过多或者耗资源过多,但又经常用到的对象。
- 有状态的工具类对象。
- 频繁访问数据库或文件的对象。
工厂方法模式
工厂方法模式通过选取相关的实现类来满足调用组件的请求,调用组件无须了解实现类的细节 与他们之间的关系。
实现方式:
优点
- 工厂方法统一了实现类的选取逻辑,避免了相关逻辑过于分散,调用组件只依赖接口或抽象类,无须料及底层实现
缺点
适用场景
当存在多个类共同实现一接口或者继承一个基类则可以使用工厂模式
抽象工厂模式
抽象工厂管理多个对象,工厂方法只管理一个对象
建造者模式
建造者模式可以将所需创建创建对象的罗技与默认配置放入一个建造者类中,这样调用组件只需少量配置数据即可创建该对象,且无需了解对象所需的默认数据值
优点
- 使创建对象所需默认值的配置变得简单
结构型模式
在解决了对象的创建问题之后,对象的组成以及对象之间的依赖关系就成了开发人员关注的焦点,因为如何设计对象的结构、继承和依赖关系会影响到后续程序的维护性、代码的健壮性、耦合性等。对象结构的设计很容易体现出设计人员水平的高低
(除了介绍的模式外还有桥接模式、外观模式和享元模式)
适配器模式
通过引用适配器使两个api不兼容的组件协作
优点
- 适配器可以将无法修改源代码的组件集成到应用中在使用第三方框架或者另一个项目输出数据时通常会遇到组件兼容问题使用此模式可以解决此问题
注意:
- 不要拓展此模式强行让待适配的api 提供该组件原来并没有的功能
装饰器模式
不修改对象所属类或对象使用者情况下修改单个对象的行为
protocol ICar {
func move()
}
class Car: ICar {
init() {
}
func move() {
print("地上跑")
}
}
class SuperCar: ICar {
private var car:ICar!
convenience init(superCar:ICar) {
self.init()
self.car = superCar
}
func move() {
car.move()
}
}
class FlyCar: SuperCar {
convenience init(flyCar:ICar) {
self.init(superCar: flyCar)
}
override func move() {
super.move()
fly()
}
func fly() {
print("天上飞")
}
}
class SwimCar: SuperCar {
convenience init(swimCar:ICar) {
self.init(superCar: swimCar)
}
override func move() {
super.move()
swim()
}
func swim() {
print("水里游")
}
}
let car = Car()
car.move()
let flyCar = FlyCar.init(flyCar: car)
flyCar.move()
let swimCar = SwimCar.init(swimCar: flyCar)
swimCar.move()
组合模式
组合模式能够将对象以树形结构组织起来,使得外界对单个对象和组合对象的使用具有一致性
代理模式
一个对象或者一个远程服务调用组件在代理对象上进行操作,反过来作用于底层资源
装饰器模式和适配器模式的相同点和异同点
装饰器模式强调的是通过组合来动态扩展对象功能。这里的动态我觉得值得深入地思考一下,比如可以通过容器来往被装饰类中添加各种功能。动态添加
动态地给一个对象添加一些额外的职责。就增加功能来说,装饰器模式相比生成子类更为灵活。
适配器模式:将一个类的接口转换成客户希望的另外一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
在我的印象中,适配器模式一直是这样的:
如同硬件驱动的实现,由于各厂商的硬件设备不一样,所以操作系统给出了一个适配器,并由各厂商实现各自的驱动。
代理模式与两者区别:
1、和适配器模式的区别:适配器模式主要改变所考虑对象的接口,而代理模式不能改变所代理类的接口。
2、和装饰器模式的区别:装饰器模式为了增强功能,而代理模式是为了加以控制。
但是经过今天的学习之后,我发现适配器模式并不如同我之前理解的那样。
使用适配器是为了使得两个接口不兼容的类能够在不修改各自的接口的前提下顺利地协同工作。举一个经典例子大家就都明白了: 引用文章,如有侵权请联系并会删除下面的demo
想像这样的一个场景,家里有一个2孔插座,现在你有一部热水壶想烧水,但是热水壶的插头是3叉的,怎么解决呢?这个时候就需要适配器了。 一个简单的适配器就是夹在热水壶插头跟插座之间,一边有3孔插座,另一头是二叉插头的东东。
好了,现在用代码来表达一下这个场景吧,这样会更清晰一些:
class 电器{
在三孔插头下工作(火线,地线,零线){
System.out.println("在三孔插头下工作") ;
}
}
interface 插座{ 在两孔插头下工作(地线,零线);}
class 适配后的电器 extends 电器 implements 插座{
在两孔插头下工作(地线,零线){
火线="";
在三孔插头下工作(火线,地线,零线)
}
}
不过这时候大家可能有疑问,就是以上的代码实现,并不是实现了一个独立的转换器(适配器)啊,而等于是在原来的电器类的外面包了一层,使得接口可用。关于这里,我在网上查到的资料是这样说的:
在这里有三个重要的概念,一个是Target 目标,在例中是插座;一个是Adaptee 被适配者,在例中是电器;还有一个是Adapter,在例中是适配后的电器
“单词的直译很容易给没有学过“适配器模式”的同学一些误导,GOF23更多的从他的转换原理类似适配器的工作原理,而事实上并没有转换器这个对象存在,我们还是从它的工作意图并结合类图上来加深理解。”
到底是不是这样,我还要去阅读设计模式原作者的书才好下定论。欢迎大家指正。
本来这个时候已经基本上解决问题,但是事实上,这并不是最好的解决方案。GOF中将上面的方法称做类适配器,即使用继承了实现适配器,另外一种适配器叫做是对象适配器。有一条设计原则是说:“使用组合而不是继承来降低对象之间的耦合”--(看来大牛们都是不支持类继承的啊)
对象适配器的实现也很简单,还是用回上面那个经典例子:
class 电器{
在三孔插头下工作(火线,地线,零线){
System.out.println("在三孔插头下工作") ;
}
}
interface 插座{ 在两孔插头下工作(地线,零线);}
class 适配后的电器 implements 插座{
电器 电器A = new 电器();
在两孔插头下工作(地线,零线){
火线="";
电器A.在三孔插头下工作(火线,地线,零线);
}
}
看,这样以来,适配后的电器类就没有继承电器类,而是直接在类中组合一个电器进来。的而且确,组合和继承是有这样的微妙的联系,即使用他们的任何一种都可以实现一样的重用效果。
(完了,发现越来越能理解那天那篇批判继承的文章了,大牛们的思想果然是高远啊!)
还有一点是有一种简化的适配器实现,即将Adaptee作为参数传入,我个人认为,这应该也是java的io包中的实现方法,就看这个例子就知道了:InputStreamReader(InputStream in) ,没错,io包中字节流对象和字符流对象之间就是应用了适配器模式,InputStream是Adaptee,InputStreamReader是Adapter。 OMG!
参考文章适配器模式和装饰器模式
行为型模式
在对象的结构和对象的创建问题都解决了之后,就剩下对象的行为问题了,如果对象的行为设计的好,那么对象的行为就会更清晰,它们之间的协作效率就会提高。其中包括 观察者模式(KVO) 状态模式、策略模式、责任链模式、命令模式、访问者模式、调停者模式、备忘录模式、迭代器模式、解释器模式、模板方法模式。等
健壮性又称鲁棒性,是指软件对于规范要求以外的输入情况的处理能力。所谓健壮的系统是指对于规范要求以外的输入能够判断出这个输入不符合规范要求,并能有合理的处理方式。
另外健壮性有时也和容错性,可移植性,正确性有交叉的地方。比如,一个软件可以从错误的输入推断出正确合理的输入,这属于容错性量度标准,但是也可以认为这个软件是健壮的。
对象池模式
对象池模式是单例模式的一种变体,他可以为多个组件提供完全多个完全相同的对象,而非单个对象,Pool类,准确的说是Pool<T>,
初始化时将传入一个对象组合,让其负责管理,Pool类的初始化器会把传进来的对象集合复制到该类的data数组,这个数组就像是一
个队列。对象池处理卡好并发请求非常重要。
总的来说,append一个元素的时间是固定的,但是多个元素的添加代价是幂级数增长,增加元素意味着需要从新开辟更大的内存swift
将原来内存的元素复制过去,所以手册建议尽量在数组建立的时候就确定其长度,从而可以避免一些开销如果要添加多个元素先应该使用
reserveCapacity(:)一次性分配好内存,可以避免中间的内存重新分配(因为如果多个元素依次添加的话,可能会经历很多次
reallocation过程
convenience init(items:[T]) {
self.init()
self.data = items
)
data.reserveCapacity(data.count)
for item in items {
data.append(item)
}
semaphore = DispatchSemaphore.init(value: data.count)
}
override init() {
super.init()
}
func getFromPool() -> T? {
var result:T?
if data.count > 0 {
DispatchQueue.main.sync {
/// 移除数组的第0个对象 并返回该对象
result = data.remove(at: 0)
}
}
return result
}
func returnToPool(item:T) {
DispatchQueue.main.sync { [weak self] in
/// 移除数组的第0个对象 并返回该对象
self?.data.append(item)
}
}
上面虽然实现了一个简单的对象池,但是在处理高并发的时候却没有那么的出色
let arrayQ = DispatchQueue.init(label: "arrayQ")
var semaphore:DispatchSemaphore!
convenience init(items:[T]) {
self.init()
self.data = items
/**总的来说,append一个元素的时间是固定的,但是多个元素的添加代价是幂级数增长,
增加元素意味着需要从新开辟更大的内存,swift将原来内存的元素复制过去,
所以手册建议尽量在数组建立的时候就确定其长度,从而可以避免一些开销
,如果要添加多个元素首先应该使用reserveCapacity(:)一次性分配好内存
,可以避免中间的内存重新分配(因为如果多个元素依次添加的话
,可能会经历很多次reallocation过程)**/
data.reserveCapacity(data.count)
for item in items {
data.append(item)
}
semaphore = DispatchSemaphore.init(value: data.count)
}
override init() {
super.init()
}
///MARK:-
/**通过添加到同步线程处理并发
func getFromPool() -> T? {
var result:T?
if data.count > 0 {
DispatchQueue.main.sync {
/// 移除数组的第0个对象 并返回该对象
result = data.remove(at: 0)
}
}
return result
}
func returnToPool(item:T) {
DispatchQueue.main.sync { [weak self] in
/// 移除数组的第0个对象 并返回该对象
self?.data.append(item)
}
}
**/
///MARK:- 确保每次请求都能请求到对象 虽然做了并发请求,我们需要将对后去对象的闭包添加到队列之前对数组需要做检查,判断是否有可用数组
func getFromPool() -> T? {
var result:T?
///信号计数器为0时候卡()线程
semaphore.wait()
arrayQ.sync { [weak self] in
result = self!.data.remove(at: 0)
}
return result
}
func returnToPool(item:T) {
arrayQ.async { [weak self] in
///信号计数器 + 1 同事时也可以让卡住的线程继续运行
self?.semaphore.signal()
}
}
END
网友评论