设计模式分类
设计模式分为三大类:
创建型模式 共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。
结构型模式 共七种:适配器模式、桥接模式、组合模式、装饰器模式、外观模式、享元模式、代理模式。
行为型模式 共十一种:责任链模式、命令模式、解释器模式、迭代器模式、中介者模式、备忘录模式、观察者模式、状态模式、策略模式、模板方法模式、访问者模式。
***********创建型模式***********
工厂模式
意图:定义一个创建对象的接口,让其子类自己决定实例化哪一个工厂类,工厂模式使其创建过程延迟到子类进行。
优点:
- 一个调用者想创建一个对象,只要知道其名称就可以了。
- 扩展性高,如果想增加一个产品,只要扩展一个工厂类就可以。
- 屏蔽产品的具体实现,调用者只关心产品的接口。
缺点:
每次增加一个产品时,都需要增加一个具体类和对象实现工厂,使得系统中类的个数成倍增加,在一定程度上增加了系统的复杂度,同时也增加了系统具体类的依赖。这并不是什么好事。
实现思路与关键代码:
- 让其子类实现工厂接口,返回的也是一个抽象的产品。
- 创建过程在其子类执行。
抽象工厂模式
意图:提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。
优点:
当一个产品族中的多个对象被设计成一起工作时,它能保证客户端始终只使用同一个产品族中的对象。
缺点:
产品族扩展非常困难,要增加一个系列的某一产品,既要在抽象的 Creator 里加代码,又要在具体的里面加代码。
实现思路与关键代码:
- 在一个产品族里面,定义多个产品。
- 在一个工厂里聚合多个同类产品。
单例模式
意图:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
优点:
- 在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例(比如管理学院首页页面缓存)。
- 避免对资源的多重占用(比如写文件操作)。
缺点:
没有接口,不能继承,与单一职责原则冲突。
实现思路与关键代码:
- 判断系统是否已经有这个单例,如果有则返回,如果没有则创建。
- 构造函数是私有的。
建造者模式
意图:将一个复杂的构建与其表示相分离,使得同样的构建过程可以创建不同的表示。
优点:
- 建造者独立,易扩展。
- 便于控制细节风险。
缺点:
- 产品必须有共同点,范围有限制。
- 如内部变化复杂,会有很多的建造类。
实现思路与关键代码:
- 将变与不变分离开。
- 建造者:创建和提供实例,导演:管理建造出来的实例的依赖关系。
原型模式
意图:用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
优点:
- 性能提高。
- 逃避构造函数的约束。
缺点:
- 配备克隆方法需要对类的功能进行通盘考虑。
- 必须实现 Cloneable 接口。
实现思路与关键代码:
- 利用已有的一个原型对象,快速地生成和原型对象一样的实例。
- 实现克隆操作,在 JAVA 实现 Cloneable 接口,重写 clone()。
***********结构型模式***********
适配器模式
意图:将一个类的接口转换成客户希望的另外一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
优点:
- 可以让任何两个没有关联的类一起运行。
- 提高了类的复用。
- 增加了类的透明度。
- 灵活性好。
缺点:
- 过多地使用适配器,会让系统非常零乱。
- 由于 JAVA 至多继承一个类,所以至多只能适配一个适配者类,而且目标类必须是抽象类。
实现思路与关键代码:
- 继承或依赖(推荐)。
- 适配器继承或依赖已有的对象,实现想要的目标接口。
桥接模式
意图:将抽象部分与实现部分分离,使它们都可以独立的变化。
优点:
- 将抽象部分与实现部分分离,使它们都可以独立的变化。
- 优秀的扩展能力。
- 实现细节对客户透明。
缺点:
桥接模式的引入会增加系统的理解与设计难度,由于聚合关联关系建立在抽象层,要求开发者针对抽象进行设计与编程。
实现思路与关键代码:
- 把这种多角度分类分离出来,让它们独立变化,减少它们之间耦合。
- 抽象类依赖实现类。
组合模式
意图:将对象组合成树形结构以表示"部分-整体"的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。
优点:
- 高层模块调用简单。
- 节点自由增加。
缺点:
在使用组合模式时,其叶子和树枝的声明都是实现类,而不是接口,违反了依赖倒置原则。
实现思路与关键代码:
- 树枝和叶子实现统一接口,树枝内部组合该接口。
- 树枝内部组合该接口,并且含有内部属性 List,里面放 Component。
装饰器模式
意图:动态地给一个对象添加一些额外的职责。就增加功能来说,装饰器模式相比生成子类更为灵活。
优点:
装饰类和被装饰类可以独立发展,不会相互耦合,装饰模式是继承的一个替代模式,装饰模式可以动态扩展一个实现类的功能。
缺点:
多层装饰比较复杂。
实现思路与关键代码:
- 将具体功能职责划分,同时继承装饰者模式。
- ①Component 类充当抽象角色,不应该具体实现。 ②修饰类引用和继承 Component 类,具体扩展类重写父类方法。
外观模式
意图:为子系统中的一组接口提供一个统一的接口。
优点:
- 减少系统相互依赖。
- 提高灵活性。
- 提高了安全性。
缺点:
不符合开闭原则,如果要改东西很麻烦,继承重写都不合适。
实现思路与关键代码:
- 客户端不与系统耦合,外观类与系统耦合。
- 在客户端和复杂系统之间再加一层,这一层将调用顺序、依赖关系等处理好。
享元模式
意图:运用共享技术有效地支持大量细粒度的对象。
优点:
大大减少对象的创建,降低系统的内存,使效率提高。
缺点:
提高了系统的复杂度,需要分离出外部状态和内部状态。
实现思路与关键代码:
- 用唯一标识码判断,如果在内存中有,则返回这个唯一标识码所标识的对象。
- 用 HashMap 存储这些对象。
代理模式
意图:为其他对象提供一种代理以控制对这个对象的访问。
优点:
- 职责清晰。
- 高扩展性。
- 智能化。
缺点:
- 由于在客户端和真实主题之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢。
- 实现代理模式需要额外的工作,有些代理模式的实现非常复杂。
实现思路与关键代码:
***********行为型模式***********
职责链模式
意图:将对象连成一条链,并沿着这条链传递请求,直到有一个对象处理它。
优点:
- 降低耦合度。它将请求的发送者和接收者解耦。
- 简化了对象。使得对象不需要知道链的结构。
- 增强给对象指派职责的灵活性。通过改变链内的成员或者调动它们的次序,允许动态地新增或者删除责任。
- 增加新的请求处理类很方便。
缺点:
- 不能保证请求一定被接收。
- 系统性能将受到一定影响,而且在进行代码调试时不太方便,可能会造成循环调用。
- 可能不容易观察运行时的特征,有碍于除错。
实现思路与关键代码:
- 拦截的类都实现统一接口。
- Handler 里面聚合它自己,在 HandlerRequest 里判断是否合适,如果没达到条件则向下传递,向谁传递之前 set 进去。
命令模式
意图:将请求封装为对象,就可以保存、传递命令,以及支持可撤销的操作。
优点:
- 降低了系统耦合度。
- 新的命令可以很容易添加到系统中去。
缺点:
使用命令模式可能会导致某些系统有过多的具体命令类。
实现思路与关键代码:
- 通过调用者调用接受者执行命令,顺序:调用者→命令→接受者。
- 定义三个角色:1、received 真正的命令执行对象 2、Command 3、invoker 使用命令对象的入口。
解释器模式
意图:可以解释自定义语法表示的解释器。
优点:
- 可扩展性比较好,灵活。
- 增加了新的解释表达式的方式。
- 易于实现简单文法。
缺点:
- 可利用场景比较少。
- 对于复杂的文法比较难维护。
- 解释器模式会引起类膨胀。
- 解释器模式采用递归调用方法。
实现思路与关键代码:
- 构建语法树,定义终结符与非终结符。
- 构建环境类,包含解释器之外的一些全局信息,一般是 HashMap。
迭代器模式
意图:提供一种方法顺序访问一个聚合对象中各个元素, 而又无须暴露该对象的内部表示。
优点:
- 它支持以不同的方式遍历一个聚合对象。
- 迭代器简化了聚合类。
- 在同一个聚合上可以有多个遍历。
- 在迭代器模式中,增加新的聚合类和迭代器类都很方便,无须修改原有代码。
缺点:
由于迭代器模式将存储数据和遍历数据的职责分离,增加新的聚合类需要对应增加新的迭代器类,类的个数成对增加,这在一定程度上增加了系统的复杂性。
实现思路与关键代码:
- 把在元素之间游走的责任交给迭代器,而不是聚合对象。
- 定义接口:hasNext, next。
中介者模式
意图:用一个中介对象来封装一系列的对象交互,中介者使各对象不需要显式地相互引用。
优点:
- 降低了类的复杂度,将一对多转化成了一对一。
- 各个类之间的解耦。
- 符合迪米特原则。
缺点:
中介者会庞大,变得复杂难以维护。
实现思路与关键代码:
- 网状结构分离为星型结构。
- 对象 Colleague 之间的通信封装到一个类中单独处理。
备忘录模式
意图:捕获一个对象的内部状态,并在该对象之外保存这个状态。
优点:
- 给用户提供了一种可以恢复状态的机制,可以使用户能够比较方便地回到某个历史的状态。
- 实现了信息的封装,使得用户不需要关心状态的保存细节。
缺点:
消耗资源。如果类的成员变量过多,势必会占用比较大的资源,而且每一次保存都会消耗一定的内存。
实现思路与关键代码:
- 通过一个备忘录类专门存储对象状态。
- 客户不与备忘录类耦合,与备忘录管理类耦合。
观察者模式
意图:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
优点:
- 观察者和被观察者是抽象耦合的。
- 建立一套触发机制。
缺点:
- 如果一个被观察者对象有很多的直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。
- 如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃。
- 观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化。
实现思路与关键代码:
- 使用面向对象技术,可以将这种依赖关系弱化。
- 在抽象类里有一个 ArrayList 存放观察者们。
状态模式
意图:允许对象在内部状态发生改变时改变它的行为,对象看起来好像修改了它的类。
优点:
- 封装了转换规则。
- 枚举可能的状态,在枚举状态之前需要确定状态种类。
- 将所有与某个状态有关的行为放到一个类中,并且可以方便地增加新的状态,只需要改变对象状态即可改变对象的行为。
- 允许状态转换逻辑与状态对象合成一体,而不是某一个巨大的条件语句块。
- 可以让多个环境对象共享一个状态对象,从而减少系统中对象的个数。
缺点:
- 状态模式的使用必然会增加系统类和对象的个数。
- 状态模式的结构与实现都较为复杂,如果使用不当将导致程序结构和代码的混乱。
- 状态模式对"开闭原则"的支持并不太好,对于可以切换状态的状态模式,增加新的状态类需要修改那些负责状态转换的源代码,否则无法切换到新增状态,而且修改某个状态类的行为也需修改对应类的源代码。
实现思路与关键代码:
- 将各种具体的状态类抽象出来。
- 通常命令模式的接口中只有一个方法。而状态模式的接口中有一个或者多个方法。而且,状态模式的实现类的方法,一般返回值,或者是改变实例变量的值。也就是说,状态模式一般和对象的状态有关。实现类的方法有不同的功能,覆盖接口中的方法。状态模式和命令模式一样,也可以用于消除 if...else 等条件选择语句。
策略模式
意图:定义一系列的算法,把它们一个个封装起来, 并且使它们可相互替换。
优点:
- 算法可以自由切换。
- 避免使用多重条件判断。
- 扩展性良好。
缺点:
- 策略类会增多。
- 所有策略类都需要对外暴露。
实现思路与关键代码:
- 将这些算法封装成一个一个的类,任意地替换。
- 实现同一个接口。
模板模式
意图:定义一个操作中的算法的骨架。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
优点:
- 封装不变部分,扩展可变部分。
- 提取公共代码,便于维护。
- 行为由父类控制,子类实现。
缺点:
每一个不同的实现都需要一个子类来实现,导致类的个数增加,使得系统更加庞大。
实现思路与关键代码:
- 将这些通用算法抽象出来。
- 在抽象类实现,其他步骤在子类实现。
访问者模式
意图:主要将数据结构与数据操作分离。
优点:
- 符合单一职责原则。
- 优秀的扩展性。
- 灵活性。
缺点:
- 具体元素对访问者公布细节,违反了迪米特原则。
- 具体元素变更比较困难。
- 违反了依赖倒置原则,依赖了具体类,没有依赖抽象。
实现思路与关键代码:
- 在被访问的类里面加一个对外提供接待访问者的接口。
- 在数据基础类里面有一个方法接受访问者,将自身引用传入访问者。