1.定义
设计模式是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、提高代码的可靠性。
2.基本要素
设计模式一般有如下几个基本要素:模式名称、问题、目的、解决方案、效果、实例代码和相关设计模式,其中模式名称、问题、解决方案和效果是关键要素。
3.分类
(1)根据其目的可分为创建型、结构型、行为型三种:
①创建型模式主要用于创建对象,对类的实例化过程进行了抽象,能够将软件模块中对象的创建和对象的使用分离。GoF提供了5种创建型模式,分别是工厂方法模式、抽象工厂模式、建造者模式、原型模式和单例模式。
②结构型模式主要用于处理类或对象的组合,描述如何将类或者对象结合在一起形成更大的结构,GoF提供了7种结构型模式,分别是适配器模式、桥接模式、组合模式、装饰模式、外观模式、享元模式和代理模式。
③行为型模式主要用于描述对类或对象怎样交互和分配职责,是对在不同的对象之间划分责任和算法的抽象化,GoF提供了11种行为型模式,分别是职责链模式、命令模式、解释器模式、迭代器模式、中介者模式、备忘录模式、观察者模式、状态模式、策略模式、模板方法模式和访问者模式。
(2)根据范围,即模式主要是用于处理类之间关系还是处理对象之间的关系,可分为类模式和对象模式两种:
①类模式处理类和子类之间的关系,这些关系通过继承建立,在编译时刻就被确定下来,是属于静态的。
②对象模式处理对象间的关系,这些关系在运行时刻变化,更具动态性。
范围\目的 | 创建型模式 | 结构型模式 | 行为型模式 |
---|---|---|---|
类模式 | 工厂方法模式 | (类)适配器模式 | 解释器模式 模板方法模式 |
对象模式 | 抽象工厂模式 建造者模式 原型模式 单例模式 | (对象)适配器模式 桥接模式 组合模式 装饰模式 外观模式 享元模式 代理模式 | 职责链模式 命令模式 迭代器模式 中介者模式 备忘录模式 观察者模式 状态模式 策略模式 访问者模式 |
4.GoF23种模式简要说明
模式 | 定义 | 优点 | 缺点 | 适用环境 |
观察者模式 | 观察者模式(Observer Pattern)定义对象间的一种一对多依赖关系,使得每当一个对象状态发生改变时,其相关依赖对象皆得到通知并被自动更新。观察者模式又叫发布-订阅模式、模型-视图模式、源-监听模式或从属者模式。观察者模式是一种对象行为型模式。 | (1)实现了表示层和数据逻辑层的分离 (2)在观察目标和观察者之间建立一个抽象的耦合 (3)观察者模式支持广播通信,观察目标会向所有注册的观察者发出通知,简化了一对多系统设计的难度 (4)符合“开闭原则” | (1)如果一个观察目标对象有很多直接或间接的观察者的话,将所有的观察者都通知到会花费很多时间 (2)如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃 (3)观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化。 | (1)一个抽象模型有两个方面,其中一个方面依赖于另一个方面 (2)一个对象的改变将导致其他一个或多个对象也发生改变,而不知道具体有多少对象将发生改变,可以降低对象之间的耦合度。 (3)一个对象必须通知其他对象,而不知道这些对象是谁。 |
命令模式 | 将一个请求封装为一个对象,从而使我们可用不同的请求对客户进行参数化;对请求排队或者记录请求日志,以及支持可撤销的操作。 | (1)降低系统的耦合度。 (2)新的命令可以很容易地加入到系统中,满足“开闭原则”。 (3)可以比较容易地设计一个命令队列和宏命令。 (4)为请求的撤销(Undo)和恢复(Redo)操作提供了一种设计和实现方案 | 使用命令模式可能会导致某些系统有过多的具体命令类。 | (1)系统需要将请求调用者和请求接收者解耦,使得调用者和接收者不直接交互。 (2)系统需要在不同的时间指定请求,将请求排队和执行请求。 (3)系统需要支持命令的撤销操作和恢复操作。 (4)系统需要将一组操作组合在一起,即支持宏命令。 |
代理模式 | 给某一个对象提供一个代理,并由代理对象控制对原对象的引用 | (1)代理模式能够协调调用者和被调用者,在一定程度上降低了系统的耦合度。 (2)远程代理使得客户端可以访问在远程机器上的对象,远程机器可能具有更好的计算机性能与处理速度,可以快速响应并处理客户端请求。 (3)虚拟代理通过使用一个小对象来代理一个大对象,可以减少系统资源的消耗,对系统进行优化并提高运行速度。 (4)保护代理可以控制对真实对象的使用权限。 | (1)由于在客户端和真实主题之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢。 (2)实现代理模式需要额外的工作,有些代理模式的实现非常复杂 | (1)远程代理:为一个位于不同的地址空间的对象提供一个本地的代理对象,这个不同的地址空间可以是在同一台主机中,也可是在另一台主机中,远程代理又叫做大使。 (2)虚拟代理:如果需要创建一个资源消耗较大的对象,先创建一个消耗相对较小的对象来表示,真实对象只在需要时才会被真正创建。 (3)Copy-on-Write代理:它是虚拟代理的一种,把复制(克隆)操作延迟到只有在客户端真正需要时才执行。一般来说,对象的深克隆是一个开销较大的操作,Copy-on-Write代理可以让这个操作延迟,只有对象被用到的时候才被克隆。 (4)保护代理:控制对一个对象的访问,可以给不同的用户提供不同级别的使用权限。 (5)缓冲代理:为某一个目标操作的结果提供临时的存储空间,以便多个客户端可以共享这些结果。 (6)防火墙代理:保护目标不让恶意用户接近。 (7)同步化代理:使几个用户能够同时使用一个对象而没有冲突。 (8)智能引用代理:当一个对象被引用时,提供一些额外的操作,如将此对象被调用的次数记录下来等。 |
适配器模式 | 将一个接口转换成客户希望的另一个接口,适配器模式使接口不兼容的那些类可以一起工作,其别名为包装器。 | (1)将目标类和适配者类解耦,通过引入一个适配者类来重用现有的适配者类,而无须修改原有代码 (2)增加了类的透明性和复用性,将具体的实现封装在适配者类中,对于客户端类来说是透明的,而且提高了适配者的复用性。 (3)灵活性和扩展性都非常好,完全符合“开闭原则”。 类适配器的优点:由于适配器类是适配者类的子类,因此可以在适配器类中置换一些适配者的方法,使得适配器的灵活性更强。 对象适配器的优点:对象适配器可以把多个不同的适配者适配到同一个目标,也就是说,同一个适配器可以把适配器类和它的子类都适配到目标接口。 | 类适配器:PHP不支持多重继承,目标抽象类只能是接口,使用具有一定的局限性。 对象适配器:与类适配器模式相比,置换适配者类的方法不容易。 | (1)系统需要使用现有的类,而这些类的接口不符合系统的需要。 (2)想要建立一个可以重复使用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作。 |
装饰模式 | 动态地给一个对象增加一些额外的职责,就增加对象功能来说,装饰模式比生成子类实现更为灵活。其别名也可以称为包装器(Wrapper),与适配器模式的别名相同,但它们适用于不同的场合。 | (1)装饰模式与继承关系的目的都是要扩展对象的功能,但是装饰模式可以提供比继承更多的灵活性。 (2)可以通过一种动态的方式来扩展一个对象的功能 (3)可以使用多个具体装饰类来装饰同一对象 (4)具体构件类与具体装饰类可以独立变化 | (1)使用装饰模式进行系统设计时将产生许多小对象,增加系统的复杂度,影响程序的性能 (2)装饰模式比继承更加易于出错,排错也很困难。 | (1)在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责。 (2)需要动态地给一个对象增加功能,这些功能也可以动态地被撤销 (3)当不能采用继承的方式对系统进行扩充或者采用继承不利于系统扩展和维护时。 |
桥接模式 | 将抽象部分与它的实现部分分离,使他们都可以独立地变化。它是一种对象结构型模式,又称为柄体模式或接口模式。 | (1)分离抽象接口及其实现部分。 (2)极大的减少了子类的个数。 (3)提高了系统的可扩展性。 (4)实现细节对客户透明,可以对用户隐藏实现细节。 | (1)桥接模式地引入会增加系统的理解与设计难度。 (2)桥接模式要求正确识别出系统中两个独立变化的维度,因此其适用范围具有一定的局限性。 | (1)如果一个系统需要在构建的抽象化角色和具体化角色之间增加更多的灵活性,避免在两个层次之间建立静态的继承联系,通过桥接模式可以使它们在抽象层建立一个关联关系 (2)抽象化角色和实现化角色可以以继承的方式独立扩展而互不影响 (3)一个类存在两个独立变化的维度 (4)不希望使用继承或因为多层次继承而导致系统类地个数急剧增加的系统 |
单例模式 | 确保在系统中某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例 | (1)提供了对唯一实例的受控访问 (2)可以节约系统资源,提供系统的性能 (3)允许可变数目的实例。 | (1)由于单例模式中没有抽象层,因此单例类的扩展有很大的困难。 (2)单例类的职责过重,在一定程度上违背了“单一职责原则”。 (3)可能导致对象状态的丢失 | (1)系统只需要一个实例对象,或者需要考虑资源消耗太大而只允许创建一个对象 (2)客户调用类的单个实例只允许使用一个公共访问点,不能通过其他途径访问该实例 |
原型模式 | 通过给出一个原型对象来指明要创建的对象的类型,然后通过复制这个原型对象的方法创建出更多同类型的对象 | (1)可以简化对象的创建过程,通过一个已有实例可以提高新实例的创建效率 (2)可以动态增加或减少产品类 (3)提供了简化的创建结构 (4)可以使用深克隆的方式保存对象的状态 | (1)违背了“开闭原则” (2)在实现深克隆时需要编写较为复杂的代码 | (1)创建新对象成本较大(如初始化需要占用较长的时间,占用太多的CPU资源或网络资源),新的对象可以通过原型模式对已有对象进行复制来获得,如果是相似对象,则可以对其属性稍作修改 (2)如果系统要保存对象的状态,而对象的状态变化很小,或者对象本身占内存不大的时候,也可以使用原型模式配合备忘录模式 (3)需要避免使用分层次的工厂类来创建分层次的对象,并且类的实例对象只有一个或很少的几个组合状态,通过复制原型对象得到新实例可能比使用构造函数创建一个新实例更加方便。 |
工厂模式 | 将类的实例化操作延迟到子类中完成,即由子类来决定实现究竟应该实例化(创建)哪一个类 | (1)工厂方法向客户隐藏了哪种具体产品类将被实例化这一细节,用户只需关心所需产品对应的工厂,无须关心创建细节,甚至无须知道具体产品类的类名。 (2)工厂可用自主确定创建何种产品对象 (3)完全符合“开闭原则”。 | (1)系统中的类的个数将成对增加,在一定程度上增加了系统的复杂度。 (2)增加了系统的抽象性和理解难度 | 1.一个类不知道它所需要的对象的类 2.一个类通过其子类来指定创建哪个对象 3.将创建对象的任务委托给多个工厂子类中的某一个,客户端在使用时可用无须关心是哪一个工厂子类创建产品子类,需要时再动态指定,可将具体工厂类的类名存储在配置文件或数据库中 |
简单工厂模式 | 在简单工厂模式中,可根据参数的不同返回不同类的实例。简单工厂模式专门定义一个类来负责创建其他类的实例,被创建的实例通常都具有共同的父类。 | (1)实现了对象创建和使用的分离 (2)客户端无需知道所创建的具体产品类的类名,只需要知道具体产品类所对应的参数即可,对于一些复杂的类名,通过简单工厂模式可用减少使用者的记忆量。 (3)通过引入配置文件,可以在不修改任何客户端代码的情况下更换和增加新的具体产品类,在一定程度上提高了系统的灵活性 | (1)工厂类的职责过重。 (2)使用简单工厂模式会增加系统中类的个数,在一定程度上增加了系统的复杂度和理解难度。 (3)系统扩展困难,一旦添加新产品就不得不修改工厂逻辑,在产品类型较多时,有可能造成工厂逻辑过于复杂,不利于系统的扩展和维护。 (4)简单工厂模式由于使用了静态工厂方法,造成工厂角色无法形成基于继承的等级结构。 | (1)工厂类负责创建的对象比较少;由于创建的对象较少,不会造成工厂方法中的业务逻辑太过复杂。 (2)客户端只知道传入工厂类的参数,对于如何创建对象不关心。 |
抽象工厂模式 | 提供了一个创建一系列相关或相互依赖对象的接口,而无须指定它们具体的类 | (1)隔离了具体类的生成,使得客户并不需要知道什么被创建。 (2)当一个产品族中的多个对象被设计成一起工作时,它能够保证客户端始终只使用同一个产品族中的对象 (3)增加新的具体工厂和产品族很方便,符合“开闭原则” | 在添加新的产品对象时,难以扩展抽象工厂来生产新种类的产品 | (1)用户无须关心对象的创建过程,将对象的创建和使用解耦 (2)系统中有多余一个的产品族,而每次只使用其中某一产品族。 (3)属于同一产品族的产品将在一起使用,这一约束必须在系统中的设计中体现出来。 (4)系统提供一个产品类的库,所有的产品以同样的接口出现,从而使客户端不依赖于具体实现 |
5.设计模式的优点
(1)设计模式融合了众多专家的经验
(2)设计模式提供了一套通用的设计词汇和一种通用的语言
(3)设计模式使得设计方案更加灵活,且易于修改
(4)设计模式的使用将提高软件系统的开发效率和软件质量