软件模式
软件模式是将模式的一般概念应用于软件开发领域,即软件开发的总体指导思路或参照样板。软件模式并非仅限于设计模式,还包括架构模式、分析模式和过程模式等,实际上,在软件生存期的每一个阶段都存在着一些被认同的模式。
软件模式可以认为是对软件开发这一特定“问题”的“解法”的某种统一标识,软件模式等于一定条件下的出现的问题以及解法。软件模式的基础结构由4个部分构成:
- 问题描述
- 前提条件(环境或约束条件)
- 解法
- 效果
软件模式与具体的应用领域无关,在模式发现过程中需要遵循大三律(Rule of Three),即只有经过三个以上不同类型(或不同领域)的系统的校验,一个解决方案才能从候选模式升格为模式。
设计模式
定义
设计模式(Design Pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结,使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性。
基本要素
设计模式一般有如下几个基本要素:模式名称、问题、目的、解决方案、效果、示例代码和相关设计模式,其中的关键元素包括以下四个方面:
- 模式名称(Pattern name)
- 问题(Problem)
- 解决方案(Solution)
- 效果(Consequences)
分类
根据其目的(模式是用来做什么的)可分为创建型(Creational)、**结构型(Structural)和行为型(Behavioral)**三种:
- 创建型模式主要用于创建对象
- 结构型模式主要用于处理类或对象的组合
- 行为型模式主要用于描述对类或对象怎样交互和怎样分配职责
跟据范围,即模式主要是用于处理类之间关系还是处理对象之间关系,可分为类模式和对象模式两种:
- 类模式处理类和子类之间的关系,这些关系通过继承建立,在编译时刻就被确定下来,是属于静态的
- 对象模式处理对象之间的关系,这些关系在运行时刻变化,更具动态性
范围/目的 | 创建型模式 | 结构型模式 | 行为型模式 |
---|---|---|---|
类模式 | 工厂方法模式 | (类)适配器模式 | 模板方法模式 |
对象模式 | 抽象工厂模式 建造者模式 原型模式 单例模式 | (对象)适配器模式 桥接模式 组合模式 装饰模式 外观模式 享元模式 代理模式 | 命令模式 迭代器模式 中介者模式 观察者模式 状态模式 策略模式 |
设计模式与类库框架
如果设计模式这么棒,为什么没人把它写成库?
设计模式比类库具有更高的(抽象)层次。设计模式告诉我们怎样构造类和对象来解决确定的问题,并且我们有责任调整这些设计来适应我们自己特定的应用
库和框架不也是设计模式吗?
框架和库不是设计模式,它们提供了我们链接到代码中的特定实现。然而,有时库和框架在其实现中使用设计模式。这太棒了,因为一旦你了解了设计模式,你就会更快速地理解那些用设计模式构建的API
设计原则
对于面向对象的软件系统设计来说,在支持可维护性的同时,需要提高系统的可复用性
软件的复用可以提高系统的开发效率,提高软件质量,节约开发成本,恰当的复用开可以改善系统的可维护性
- 单一职责原则要求在软件系统中,一个类只负责一个功能领域中的相应职责
- 开闭原则要求一个软件实体应当对扩展开放,对修改关闭,即在不修改源代码的基础上扩展一个系统的行为
- 里氏代换原则则可以通俗地表述为在软件中如果能够使用基类对象,那么一定能够使用其子类对象
- 依赖倒转原则要求抽象不应该依赖于细节,细节应该依赖于抽象;要对接口编程,不要针对实现编程
- 接口隔离原则要求客户端不应该依赖那些它不需要的接口,即将一些大的接口细化成一些小的接口供客户端使用
- 合成复用原则要求复用时尽量使用对象组合,而不使用继承
- 迪米特法则要求一个软件实体应当尽可能少地与其他实体发生相互作用
原则分类
目标:开闭原则
指导:最小知识原则
基础:单一职责原则、可变性封装原则
实现:依赖倒转原则、合成复用原则、里氏代换原则、接口隔离原则
具体设计模式介绍
策略模式
优点
- 对“开闭原则”的完美支持,在不修改原有系统的基础上可以更换算法或者增加新的算法
- 它很好地管理算法族,提高了代码的复用性,是一种替换继承,避免多重条件转移语句的实现方式
缺点
- 客户端必须知道所有的类,并理解其区别
- 同时在一定程度上增加了系统中类的个数,可能会存在很多策略类
适用情况
- 在一个系统中有许多类,它们之间的区别仅在于它们的行为,使用策略模式可以动态地让一个对象在许多行为中选择一种行为
- 一个系统需要动态地在几种算法中选择一种
- 避免使用难以维护的多重条件选择语句
- 希望在具体策略类中封装算法与相关数据结构
简单工厂模式
创建型模式对类的实例化过程进行了抽象,能够将对象的创建与对象的使用过程分离
简单工厂模式又称为静态工厂方法模式,它属于类创建型模式。在简单工厂模式中,可以根据参数的不同返回不同的实例。简单工厂模式专门定义一个类来负责创建其他类的实例,被创建的实例通常都具有共同的父类
简单工厂模式包含3个角色:
- 工厂角色负责实现创建所有实例的内部逻辑
- 抽象产品角色是所创建的所有对象的父类,负责描述所有实例所共有的公共接口
- 具体产品角色是创建目标,所有创建的对象都充当这个角色的某个具体类的实例
简单工厂模式的要点在于:传入正确参数即可获得所需对象,而无需知道创建细节
优点
最大优点在于实现对象的创建和对象的使用分离,将对象的创建交给专门的工厂类负责,
缺点
其最大的缺点在于工厂类不够灵活,增加新的具体产品需要修改工厂类的判断逻辑代码,而且产品较多时,工厂方法会非常复杂
适用情况
- 工厂类负责创建的对象比较少
- 客户端只知道传入工厂类的参数,对于如何创建对象不关心
工厂方法模式
工厂方法模式又称为工厂模式,它属于创建型模式。在工厂方法模式中,工厂父类负责定义创建产品对象的公共接口,而子类工厂则负责生成成具体的产品对象,这样做的目的是将产品类的实例化操作延迟到工厂子类中完成,即通过工厂子类来确定应该实例化哪个类(而不是通过判断逻辑代码)
工厂方法模式包含4个角色:
- 抽象产品是定义产品的接口,是工厂方法所创建对象的超类型,即产品对象的共同父类或接口
- 具体产品实现了抽象产品接口,某种类型的具体产品由专门的具体工厂创建,它们之间往往一一对应
- 抽象工厂中声明了工厂方法,用于返回一个产品,它是工厂方法模式的核心,任何在模式中创建对象的工厂类都必须实现该接口
- 具体工厂是抽象工厂类的子类,实现了抽象工厂中定义的工厂方法,并可由客户调用,返回一个具体产品类的实例
工厂方法模式是简单工厂模式的进一步抽象和推广。由于使用了面向对象的多态性,工厂方法模式保持了简单工厂模式的优点,而克服了它的缺点。在工厂方法模式中,核心的工厂类不再负责所有产品的创建,而是将具体创建工作交给子类去做。这个核心类仅仅负责给出具体工厂必须实现的接口,而不负责产品类被实例化这种细节,这使得工厂方法模式可以允许系统在不修改工厂角色的情况下引进新新产品
优点
主要优点是新增产品类时无须修改现有系统,并封装了产品对象的创建细节,系统具有良好的灵活性和可扩展性
缺点
其缺点在于增加新产品的同时需要增加新的工厂,导致系统类的个数成对增加,在一定程度上增加了系统的复杂性
适用情况
- 一个类不知道它所需要的对象的类
- 一个类通过子类来指定创建哪个对象
- 将创建对象的任务委托给多个工厂子类中的某一个,客户端在使用时可以无须关心是哪一个工厂子类创建产品子类,需要时再动态指定
抽象工厂模式
抽象工厂模式提供一个创建一系列相关或相互依赖对象的接口,而无须指定它们具体的类。抽象工厂模式又称为Kit模式,属于对象创建型模式
抽象工厂模式包括4个角色:
- 抽象工厂用于声明生成抽象产品的方法
- 具体工厂实现了抽象工厂声明的生成抽象产品的方法,生成一组具体的产品,这些产品构成了一个产品族,每一个产品都位于某个产品等级结构中
- 抽象产品为每种产品的声明接口,在抽象产品中定义了产品的抽象业务方法
- 具体产品定义具体工厂生成的具体产品对象,实现抽象产品接口中定义的业务方法
抽象工厂模式是所有形式的工厂模式种最为抽象和最具一般性的一种形态。抽象工厂模式与工厂方法模式最大的区别在于:工厂方法模式针对的是一个产品等级结构,而抽象工厂模式则需要面对多个产品等级结构(说白了就是工厂子类实现了抽象工厂中定义的生产不同产品的接口,而工厂方法是实现同一个)
优点
- 隔离了具体类的生成,使得客户并不需要知道什么被创建
- 每次可以通过具体工厂类创建一个产品族中的多个对象
- 增加或者替换产品族比较方便,增加新的具体工厂和产品族很方便
缺点
- 增加新的产品等级结构很复杂,需要修改抽象工厂和所有的具体工厂类,对“开闭原则”的支持呈现倾斜性
适用情况
- 一个系统不应当依赖于产品类实例如何被创建、组合和表达的细节
- 系统中有多于一个的产品族,而每次只使用其中某一个产品族
- 属于同一个产品族的产品将在一起使用
- 系统提供一个产品类的库,所有的产品以同样的接口出现,从而使客户端不依赖于具体实现
建造者模式(Builder)
建造者模式将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。建造者模式是一步一步创建一个复杂的对象,它允许用户只通过指定复杂对象的类型和内容就可以构建它们,用户不需要知道内部的具体构建细节。建造者模式属于对象创建型模式
建造者模式包含如下4个角色:
- 抽象建造者为创建一个产品对象的各个部件指定抽象接口
- 具体建造者实现了抽象建造者接口,实现各个部件的构造和装配方法,定义并明确它所创建的复杂对象,也可以提供一个方法返回创建好的复杂产品对象
- 产品角色是被构建的复杂对象,包含多个组成部件
- 指挥者负责安排复杂对象的建造次序,指挥者与抽象建造者之间存在关联关系,可以在其construct() 建造方法中调用建造者对象的部件构造与装配方法,完成复杂对象的建造
在建造者模式的结构中引入了一个指挥者类,该类的主要作用有两个:
- 一方面它隔离了客户与生产过程
- 另一方面它负责控制产品的生成过程
指挥者对针对建造抽象者编程,客户端只需要知道具体建造者的类型,即可通过指挥者类调用建造者的相关方法,返回一个完整的产品对象
优点
主要优点在于客户端不需要知道产品内部组成的细节,将产品本身与产品的创建过程解耦,使得相同的创建过程可以创建不同的产品对象,每一个具体建造者都相对独立,与其他具体建造者无关,因此可以很方便地替换具体建造者或增加新的具体建造者,符合“开闭原则”,还可以更加精细的控制产品的创建过程
缺点
主要缺点在于由于建造者模式所创建的产品一般具有较多的共同点,其组成部分类似,因此其使用范围受到一定的限制,如果产品内部变化复杂,可能会导致需要定义很多具体建造者来实现这种变化,导致系统变得庞大
适用范围
- 需要生产的产品对象有复杂的内部结构,这些产品对象通常包含多个成员属性
- 需要生成的产品对象属性相互依赖,需要指定其生成顺序
- 对象的创建过程独立于创建该对象的类
- 隔离复杂对象的创建和使用,并使得相同的创建过程可以创建不同的产品
原型模式
在浅克隆中,当对象被复制时,它所包含的成员对象却没有被复制
在深克隆中,除了对象本身被复制外,对象包含的引用也被复制,也就是其中的成员对象也将复制
在Java语言中,通过覆盖Object类中的clone()方法可以实现浅克隆
优点
最大的优点在于可以快速创建很多相同或相似的对象,简化对象的创建过程,还可以保存对象的一些中间状态
缺点
其缺点在于需要为没有给类配备一个克隆方法,因此对已有类进行改造比较麻烦,需要修改其源代码,并且在实现深克隆时需要编写较为复杂的代码
适用情况
- 创建新对象成本较大,新的对象可以通过原型模式对已有对象进行复制来获得
- 系统要保存对象的状态,而对象的状态变化很小
- 需要避免使用分层次的工厂类来创建分层次的对象,并且类的实例对象只有一个或很少的几个组合状态,通过原型对象得到新实例可能比使用构造函数创建一个新实例更加方便
状态模式
优点
- 封装了转换规则,并枚举可能的状态,它将所有与某个状态有关的行为放到一个类中
- 可以方便地增加新的状态,只需要改变对象状态即可改变对象的行为
- 让多个环境对象共享一个状态对象,从而减少系统中对象的个数
缺点
- 使用状态模式会增加系统类和对象的个数
- 状态模式的结构与实现都较为复杂,如果使用不当将导致程序结构和代码的混乱,对于可以切换状态的状态模式不满足“开闭原则”的要求
使用情况
- 对象的行为依赖与它的状态(属性)并且可以根据它的状态改变而改变它的相关行为
- 代码中包含大量与对象状态有关的条件语句,这些条件语句的出现,会导致代码的可维护性和灵活性变差,不能方便地增加和删除状态,使客户类与类库之间的耦合增强
命令模式
在命令模式中,将一个请求封装为一个对象,从而使我们可用不同的请求对客户进行参数化;对请求排队或者记录请求日志,以及支持可撤销的操作。命令模式是一种对象行为型模式,其别名为动作模式或事务模式
命令模式包含四个角色:
- 抽象命令类中声明了用于执行请求的execute() 等方法,通过这些方法可以调用请求接收者的相关操作
- 具体命令类是抽象命令类的子类,实现了在抽象命令类中声明的方法,它对应具体的接收者对象,将接收者对象的动作绑定其中
- 调用者即请求的发送者,又称为请求者,它通过命令对象来执行请求
- 接收者执行与请求相关的操作,它具体实现对请求的业务处理
命令模式的本质是对命令进行封装,将发出命令的责任和执行命令的责任分隔开。命令模式使请求本身成为一个对象,这个对象和其他对象一样可以被存储和传递
优点
- 降低系统的耦合度,增加新的命令很方面
- 可以比较容易地设计一个命令队列和宏命令
- 方便地实现对请求的撤销和恢复
缺点
- 可能会导致某些系统有过多的具体命令类
适用情况
- 需要将请求调用者和请求接收者解耦,使得调用者和接收者不直接交互
- 需要在不同的时间指定请求、将请求排队和执行请求
- 需要支持命令的撤销操作和恢复操作
- 需要将一组操作组合在一起,支持宏命令
观察者模式
观察者模式定义对象间的一种一对多依赖关系,使得每当一个对象状态发生改变时,其相关依赖对象皆得到通知并被自动更新。观察者模式又叫做发布—订阅模式、模型—视图模式、源—监听器模式或者从属者模式。观察者模式是一种对象行为模式
观察者模式包含四个角色:
- 目标又称为主题,它是指被观察的对象
- 具体目标是目标类的子类,通常它包含有经常发生改变的数据,当它的状态发生改变时,向它的各个观察者发出通知
- 观察者将对观察目标的改变做出反应
- 在具体观察者中维护一个指向具体目标对象的引用,它存储具体观察者的有关状态,这些状态需要和具体目标对象的状态保持一致
观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个目标对象,当这个目标对象的状态发生变化时,会通知所有观察者对象,使它们能够自动更新
优点
观察者模式的主要优点在于可以实现表示层和数据逻辑层的分离,并在观察目标和观察者之间建立一个抽象的耦合,支持广播通信
缺点
其主要缺点在于如果一个观察目标对象有很多直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间,而且如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃
适用情况
- 一个抽象模型有两个方面,其中一个方面依赖于另一个方面
- 一个对象的改变将导致其他一个或多个对象也发生改变,而不知道具体有多少对象将发生改变
- 一个对象必须通知其他对象,并且并不知道这些对象是谁
- 需要在系统中创建一个触发链
注
在JDK的java.util包中,提供了Observable类以及Observer接口,它们构成了Java语言对观察者模式的支持
中介者模式
中介者模式用一个中介对象来封装一系列的对象交互,中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。中介者模式又称为调停者模式,它是一种对象行为型模式
包含四个角色:
- 抽象中介者用于定义一个接口,该接口用于与各同事对象之间的通信
- 具体中介者使抽象中介者的子类,通过协调各个同事对象来实现协作行为,了解并维护它的各个同事对象的引用
- 抽象同事类定义各同事的公有方法
- 具体同事类是抽象同事类的子类,每一个同事对象都引用一个中介者对象
- 每一个同事对象在需要和其他同事对象通信时,先与中介者通信,通过中介者来间接完成与其他同事的通信
- 在具体同事类中实现了抽象同事类定义的方法
通过引入中介者对象,可以将系统的网状结构变成以中介者为中心的星形结构,中介者承担了中转和协调作用。中介者类是中介者模式的核心,它对整个系统进行控制和协调,简化了对象之间的交互,还可以对对象之间的交互进行进一步的控制
优点
中介者模式简化了对象之间的交互,将各同事解耦,还可以减少子类的生成,对于复杂的对象之间的交互,通过引入中介者,可以简化各同事类的设计和实现
缺点
其缺点主要在于具体中介者类中包含了同事之间的交互细节,可能会导致中介者类非常复杂,使得系统难以维护
适用情况
- 系统中对象之间存在复杂的引用关系,产生的相互依赖关系结构混乱且难以理解
- 一个对象由于引用了其他很多对象并且直接和这些对象通信,导致难以复用该对象
- 想通过一个中间类来封装多个类中的行为,而又不想生成太多的子类
模板方法模式
在模板方法模式中,定义一个操作中算法的骨架,而将一些步骤延迟到子类中,模板方法模式使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。模板方法是一种类行为模式
包含两个角色:
- 在抽象类中定义一系列基本操作,这些基本操作可以是具体的,也可以是抽象的;同时,在抽象类中实现了一个模板方法,用于定义一个算法的骨架
- 具体子类是抽象类的子类,用于实现在父类中定义的抽象基本操作以完成子类特定算法的步骤,也可以覆盖在父类中实现的基本操作
在模板方法模式中,方法可以分为模板方法和基本方法,其中基本方法又可以分为抽象方法、具体方法和钩子方法,钩子方法根据其特点又分为空方法和与实现算法步骤的基本方法“挂钩”的 方法。
优点
模板方法模式的优点在于子类定义详细的处理算法时不会改变算法的结构,实现了代码的复用,通过对子类的扩展可以增加新的行为,符合“开闭原则”
缺点
其缺点在于需要为每个不同的实现都定义一个子类,这回导致类的个数增加,系统更加庞大,设计也更加抽象
适用情况
- 一次性实现一个算法的不变的部分,并将可变1行为留给子类来实现
- 各子类中公共的行为应被提取出来并集中到一个公共父类中以避免代码重复
- 对一些复杂的算法进行分割,将其算法中固定不变的部分设计为模板方法,而一些可以改变的细节由其子类来实现
- 通过模板方法模式还可以控制子类的扩展
适配器模式
适配器模式用于将一个接口转换换成客户希望的另一个接口,适配器模式使接口不兼容的那些类可以一起工作,其别名为包装器。适配器模式既可以作为类结构型模式,也可以作为对象结构型模式
包含四个角色:
- 目标抽象类定义客户要用的特定领域的接口
- 适配器类可以调用另一个接口,作为一个转换器,对适配者和抽象目标类进行适配,它是适配器模式的核心
- 适配者类是被适配的角色,它定义了一个已经存在的接口,这个接口需要适配
- 在客户类中针对目标抽象类进行编程,在所继承的目标抽象类方法中调用适配者类相应的业务方法
在类适配器模式中,适配器类实现了目标抽象类接口并继承了适配者类,并在目标抽象类的实现方法中调用了所继承的适配者类的方法
在对象适配器模式中,适配器类继承了目标抽象类并定义了一个适配者类的对象实例,在所继承的目标抽象类方法中调用适配者类的相应业务方法
优点
将目标类和适配者类解耦,增加了类的透明性和复用性,同时系统的灵活性和扩展性都非常好,更换适配器或者增加新的适配器都非常方便,符合“开闭原则”
缺点
- 适配器类在很多编程语言中不能同时适配多个适配者类
- 对象适配器模式的缺点是很难置换适配者类的方法
适用情况
- 系统需要使用现有的类,而这些类的接口不符合系统的需要
- 想要建立一个可以重复使用的类,用于一些彼此之间没有太大关联的类一起工作
组合模式
组合模式用于组合多个对象形成树形结构以表示“整体—部分”的结构层次。 组合模式对单个对象(即叶子对象)和组合对象(即容器对象)的使用具有一致性。组合模式又可以称为“整体—部分”模式,属于对象的结构模式,它将对象组织到树结构中,可以用来描述整体与部分的关系
包含三个角色:
- 抽象构件为叶子构件和容器构件对象声明接口,在该角色中可以包含所有子类共有行为的声明和实现
- 叶子构件在组合结构中表示叶子节点对象,叶子节点没有子节点
- 容器构件在组合结构中表示容器节点对象,容器节点包含子节点,其子节点可以是叶子节点,也可以是容器节点,它提供一个集合用于存储子节点,实现了在抽象构件中定义的行为
组合模式的关键是定义了一个抽象构件类,它既可以代表叶子,又可以代表容器,而客户端针对该抽象构件类进行编程,无须直到它到底表示的是叶子还是容器,可以对其进行统一处理
优点
方便地对层次结构进行控制,客户端调用简单,客户端可以一致地使用组合结构或其中单个对象,用户就不必关心自己处理的是单个对象还是整个组合结构,简化了客户端代码
缺点
使设计变得更加抽象,且增加新构件时可能会产生一些问题,而且很难对容器中的构建类型进行限制
适用情况
- 需要表示一个对象整体或部分层次
- 让客户能够忽略不同对象层次的变化,客户端可以针对抽象构件编程,无须关心对象层次结构的细节
- 对象的结构是动态的并且复杂程度不一样,但客户需要一致地处理它们
组合模式根据抽象构件类的定义形式,又可以分为透明组合模式和安全组合模式
桥接模式
桥接模式将抽象部分与它的实现部分分离,使它们都可以独立地变化。它是一种对象结构型模式,又称为柄体模式(Handle and Body)或接口模式
包含四个角色:
- 抽象类中定义了一个实现类接口类型地对象并可以维护该对象
- 扩充抽象类扩充由抽象类定义的接口,它实现了在抽象类中定义的抽象业务方法,在扩充抽象类中可以调用在实现类接口中定义的业务方法
- 实现类接口定义了实现类的接口,实现类接口仅提供基本操作,而抽象类定义的接口可能会做个更多更复杂的操作
- 具体实现类实现了实现了实现类接口并且具体实现它,在不同的具体实现类中提供基本操作的不同实现;在程序运行时,具体实现类对香将替换父类对象,提供给客户端具体的业务操作方法
在桥接模式中,抽象化与实现化脱耦,它们可以沿着各自的维度独立变化
优点
分离抽象接口及其实现部分,是比多继承方案更好的解决办法,桥接模式还提高了系统的可扩充性,在两个变化维度这种任意扩展一个维度,都不需要修改原有系统,实现细节对客户透明,可以隐藏实现细节
缺点
增加系统的理解与设计难度,且识别出系统中两个独立变化的维度并不是一件容易地事情
适用情况
- 需要在构件的抽象化角色和具体化角色之间增加更多的灵活性,避免在两个层次之间建立静态的继承联系
- 抽象化角色和实现化角色可以以继承的方式独立扩展而互不影响
- 一个类存在两个独立变化的维度,且者两个维度都需要进行扩展
- 设计要求需要对管理抽象化角色和具体化角色
- 不希望使用集成或因为多层次继承导致系统类的个数急剧增加的系统
装饰模式
装饰模式用于动态地给一个对象增加一些额外的职责,就增加对象功能来说,装饰模式比生成子类实现更为灵活。它是一种对象结构型模式
包含四个角色:
- 抽象构件定义了对象的接口,可以给这些对象的接口,可以给这些对象动态增加职责(方法)
- 具体构件定义了具体的构件对象,实现了在抽象构件中声明的方法,装饰器可以给他增加额外的职责(方法)
- 抽象装饰类时抽象构件类的子类,用于给具体构件增加职责,但是具体职责在其子类中实现
- 具体装饰类是抽象装饰类的子类,负责向构件添加新的职责
使用装饰模式来实现扩展比继承更加灵活,它以对客户透明的方式动态地给一个对象附加更多的责任。装饰模式可以在不需要创造更多子类的情况下,将对象的功能加以扩展
public class RedShapeDecorator extends ShapeDecorator {
public RedShapeDecorator(Shape decoratedShape) {
super(decoratedShape);
}
@Override
public void draw() {
decoratedShape.draw();
setRedBorder(decoratedShape);
}
private void setRedBorder(Shape decoratedShape){
System.out.println("Border Color: Red");
}
}
public class DecoratorPatternDemo {
public static void main(String[] args) {
Shape circle = new Circle();
ShapeDecorator redCircle = new RedShapeDecorator(new Circle());
ShapeDecorator redRectangle = new RedShapeDecorator(new Rectangle());
//Shape redCircle = new RedShapeDecorator(new Circle());
//Shape redRectangle = new RedShapeDecorator(new Rectangle());
System.out.println("Circle with normal border");
circle.draw();
System.out.println("\nCircle of red border");
redCircle.draw();
System.out.println("\nRectangle of red border");
redRectangle.draw();
}
}
优点
- 可以提供比继承更多的灵活性,可以通过一种动态的方式来扩展一个对象的功能,并通过使用不同的具体装饰类以及这些装饰类的排列组合,可以创造出很多不同行为的组合
- 具体构件类与具体装饰类可以独立变化,用户可以根据需要增加新的具体构件类和具体装饰类
缺点
- 使用装饰模式进行系统设计时,将产生很多小对象,而且装饰模式比继承更加易于出错,排错也很困难,对于多次装饰的对象,调试时寻找错误可能需要逐级排查,较为繁琐
适用情况
- 在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责
- 需要动态地给一个对象增加功能,这些功能也可以被动态地撤销
- 当不能采用继承的方式对系统进行扩充或者采用继承不利于系统扩展和维护时
装饰模式可分为透明装饰模式和半透明装饰模式
- 在透明装饰模式中,要求客户端完全针对抽象编程,装饰模式的透明性要求客户端程序不应该具体声明构件类型和具体装饰类型,而应该全部声明为抽象构建类型
- 半透明装饰模式允许用户在客户端具体声明装饰者类型的对象,调用在具体装饰者中新增的方法
外观模式
在外观模式中,外部与一个子系统的通信必须通过一个同一的外观对象进行,为子系统中的一组接口提供一个一致的界面,外观模式定义了一个高层接口,这个接口使得这一子系统更加容易适用。外观模式又被称为门面模式,它是一种对象结构型模式。
包含两个角色:
- 外观角色是在客户端直接调用的角色,在外观角色中可以知道相关的(一个或多个)子系统的功能和责任,它将所有从客户端发来的请求委派到相应的子系统去,传递给相应的子系统对象处理
- 在软件系统中可以同时有一个或者多个子系统角色,每一个子系统可以不是一个单独的类,而是一个类的集合,它实现子系统的功能
外观模式要求一个子系统的外部与其内部的通信通过一个统一的外观对现象进行,外观类将客户端与子系统的内部复杂性分隔开,使得客户端只需要与外观对象打交道,而不需要与子系统内部的很多对象打交道
优点
- 对客户屏蔽子系统组件,减少了客户处理的对象数目并使得子系统使用起来更加容易,它实现了子系统与客户之间的松耦合关系
- 降低了大型软件系统中的编译依赖性,简化了系统在不同平台之间的移植过程
缺点
- 不能很好地限制客户使用子系统类
- 在不引入抽象外观类的情况下,增加新的子系统可能需要修改外观类或客户端的源码,违背了“开闭原则”
适用情况
- 要为一个复杂子系统提供一个简单接口
- 客户程序与多个子系统之间存在很大的依赖性
- 在层次化结构中,需要定义系统中每一层的入口,使得层与层之间不直接产生联系
享元模式
享元模式运用共享技术有效地支持大量细粒度对象的复用。系统只使用少量的对象,而这些对象都很相似,状态变化很小,可以实现对象的多次复用,它是一种对象结构型模式
包含四个角色:
- 抽象享元类声明一个接口,通过它可以接受并作用于外部状态
- 具体享元类实现了抽象享元接口,其实例称为享元对象
- 非共享具体享元是不能被共享的抽象享元类的子类
- 享元工厂类用于创建并管理享元对象,它针对抽象享元类编程,将各种类型的具体享元对象存储在一个享元池中
享元模式以共享的方式高效地支持大量细粒度对象,享元对象能做到共享的关键部分是区分内部状态和外部状态。
- 内部状态是存储在享元对象内部并且不会随环境改变而改变的状态,因此内部状态可以共享
- 外部状态是随环境改变而改变、不可以共享的状态
优点
极大减少内存中对象地数量,使得相同对象或相似对象在内存中只保存一份
缺点
使得系统更加复杂,并且需要将享元对象的状态外部化,而读取外部状态使得运行时间变长
适用情况
- 一个系统有大量相同或者相似的对象,由于这类对象的大量使用,造成内存的大量浪费
- 对象的大部分状态都可以外部化,可以将这些外部状态传入对象中
- 多次重复使用享元对象
代理模式
在代理模式中,要求给某一个对象提供一个代理,并由代理对象控制对原对象的引用。代理模式的英文叫做Proxy或Surrogate,它是一种对象结构型模式
包含三个角色:
- 抽象主题角色声明了真实主题和代理主题的共同接口
- 代理主题角色内部包含对真实主题的引用,从而可以在任何时侯操作真实主题对象
- 真实主题角色定义了代理角色所代表的真实对象,在真实主题角色中实现了真实的业务操作,客户端可以通过代理主题角色间接调用真实主题角色中定义的方阿飞
优点
能够协调调用者和被调用者,在一定程度上降低了系统的耦合度
缺点
- 由于在客户端和真实主题之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢
- 实现代理模式需要额外的工作,有些代理模式的实现非常复杂
适用情况
-
远程代理为一个位于不同的地址空间的对象提供一个本地的代表对象,它使得客户端可以访问在远程机器上的对象,远程机器可能具有更好的计算性能与处理速度,可以快速响应并处理客户端请求
-
如果需要创建一个资源消耗较大的对象,先创建一个消耗相对较小的对象来表示,真实对象只在需要时才会被真正创建,这个小对象称为虚拟代理。虚拟代理通过使用一个小对象来代表一个大对象,可以减少系统资源的消耗,对系统进行优化并提高运行速度
-
保护代理可以控制对一个对象的的访问,可以给不同的用户提供不同级别的使用权限
防御式编程
为什么采用防御式编程
通过预见到或至少预先推测到问题所,断定代码中每个阶段可能出现的错误,并做出相应的防范措施,来防止类似意外的发生
断言
断言处理代码中不应发生的的错误
错误处理技术
错误处理处理那些预料中可能发生的错误
异常
异常是把代码中的错误或异常传递给调用方代码的一种特殊手段
隔离程序
隔栏的使用使断言和错误处理有了清晰的区分
辅助调试代码
防御式编程的另一重要方面是使用调试助手(辅助调试的代码),从而帮助快速检测错误
如何对待防御式编程
- 过度的防御式编程也会引起问题
- 程序会变得臃肿而缓慢
- 增加了软件的复杂度
- 防御式代码同样会有缺陷
- 正确的态度
- 采用防御的姿势
- 考虑好什么地方进行防御
- 因地制宜地调整防御式编程的优先级
表驱动法
- 表驱动法是一种编程模式
- 从表里查找信息而不使用逻辑语句(if和case)
- 表驱动法适用于复杂的逻辑
- 表驱动法的另一个好处是可以将复杂逻辑从代码中独立出来,以便于单独维护
目标
原理
如何快速从表中查询条目
- 直接访问
- 索引访问
- 阶梯访问