九、装饰模式(decorator)
为对象动态添加功能。
装饰模式是一种用来代替继承的技术,无须通过继承增加子类就能拓展对象的新功能。使用对象的关联关系代替继承关系,更加灵活,同时可以避免类型体系的快速膨胀。
- Component:抽象构件,真实对象和装饰对象都有的接口,这样,客户端对象可以以与真实对象相同的方式同装饰对象交互
- ConreteComponent:具体构件对象(真实对象)
- Decorator:装饰角色。持有一个抽象构件的引用,装饰对象接受所有客户端的请求,并把这些请求转发给真实的对象。这样,就能在真实对象调用前后增加新的功能
- ConreteDecorator:具体装饰对象。负责给构件对象增加新的责任
要点: 装饰者与被装饰者拥有共同的超类,继承的目的是继承类型,而不是行为
Implementation
component抽象类:
Component是一个接口或是抽象类,就是定义我们最核心的对象,也就是最原始的对象。
1 | public abstract class Component { |
conretetComponent:
具体构件,通过继承实现Component抽象类中的抽象方法。是最核心、最原始、最基本的接口或抽象类的实现,我们要装饰的就是它。
1 | public class ConretetComponent extends Component |
Decorator装饰类:
一般是一个抽象类,在其属性里必然有一个private变量指向Component抽象构件。
1 | public abstract class Decorator extends Component { |
ConcreteDecorator类:
我们可以写多个具体实现类,把最核心的、最原始的、最基本的东西装饰成其它东西。
这里就写两个类,稍改一下二者的实现顺序,看看结果。
A类,它的operation()方法先执行了method1()方法,再执行了Decorator的operation()方法。
1 | public class ConcreteDecoratorA extends Decorator { |
B类,它的operation()方法先执行了Decorator的operation()方法,再执行了method2()方法。
1 | public class ConcreteDecoratorB extends Decorator { |
Client客户端:
1 | public class Client { |
IO流实现细节
- Component抽象构件角色:
- io流中的InputStream、OutputStream、Reader、Writer
- ConcreteComponent 具体构件角色:
- io流中的FileInputStream、FileOutputStream
- Decorator装饰角色:
- 持有一个抽象构件的引用:io流中的FilterInputStream、FilterOutputStream
- ConcreteDecorator具体装饰角色:
- 负责给构件对象增加新的责任。Io流中的BufferedOutputStream、BufferedInputStream等。
装饰模式的应用
1. 何时使用
- 在不想增加很多子类的情况下扩展类时
2. 方法
- 将具体功能职责划分,同时继承装饰者模式
3. 优点
- 装饰类和被装饰类可以独立发展,而不会相互耦合。它有效地把类的核心职责和装饰功能分开了
- 装饰模式是继承关系的一个替代方案
- 装饰模式可以动态地扩展一个实现类的功能
4. 缺点
- 多层装饰比较复杂。比如我们现在有很多层装饰,出了问题,一层一层检查,最后发现是最里层的装饰出问题了,想想工作量都害怕
5. 使用场景
- 需要扩展一个类的功能时
- 需要动态地给一个对象增加功能,并可以动态地撤销时
- 需要为一批的兄弟类进行改装或加装功能时
装饰模式和桥接模式的区别
两个模式都是为了解决过多子类对象问题。但他们的诱因不一样。桥接模式是对象自身现有机制沿着多个维度变化,是既有部分不稳定。装饰模式是为了增加新的功能。
参考文章
简说设计模式——装饰模式 - JAdam - 博客园 (cnblogs.com)
十、外观模式(facade)
提供了一个统一的接口,用来访问子系统中的一群接口,从而让子系统更容易使用。
Implementation
观看电影需要操作很多电器,使用外观模式实现一键看电影功能。
子系统角色:
1 | public class SubSystem { |
外观类:
1 | public class Facade { |
客户端:
1 | public class Client { |
设计原则
最少知识原则:只和你的密友谈话。也就是说客户对象所需要交互的对象应当尽可能少。
外观模式的应用
1. 何时使用
- 客户端不需要知道系统内部的复杂联系,整个系统只提供一个“接待员”即可
- 定义系统的入口
2. 方法
- 客户端不与系统耦合,外观类与系统耦合
3. 优点
- 减少了系统的相互依赖
- 提高了灵活性。不管系统内部如何变化,只要不影响到外观对象,任你自由活动
- 提高了安全性。想让你访问子系统的哪些业务就开通哪些逻辑,不在外观上开通的方法,你就访问不到
4. 缺点
- 不符合开不原则,修改很麻烦
5. 使用场景
- 为一个复杂的模块或子系统提供一个外界访问的接口
- 子系统相对独立,外界对子系统的访问只要黑箱操作即可
- 预防低水平人员带来的风险扩散
十一、享元模式(FlyWeight)
运用共享技术有效地支持大量细粒度的对象。
池技术:String常量池、数据库连接池、缓冲池等等都是享元模式的应用,享元模式是池技术的重要实现方式。
- FlyWeightFactory:享元工厂类。创建并管理享元对象,享元池一般设计成键值对。
- FlyWeight:抽象享元类。通常是一个接口或抽象类,声明公共方法,这些方法可以向外界提供对象的内部状态,设置外部状态。
- ConcreteFlyWeight:具体享元类。为内部状态提供成员变量进行存储
- UnsharedConcreteFlyWeight:非共享享元类。不能被共享的子类可以设计为非共享享元类
Implementation
Flyweight抽象类
所有具体享元类的超类或接口,通过这个接口,Flyweight可以接受并作用于外部状态。
1 | public abstract class Flyweight { |
ConcreteFlyweight类
继承Flyweight超类或实现Flyweight接口,并为其内部状态增加存储空间。
1 | public class ConcreteFlyweight extends Flyweight { |
UnsharedConcreteFlyweight类
指那些不需要共享的Flyweight子类。
1 | public class UnsharedConcreteFlyweight extends Flyweight { |
FlyweightFactory类
一个享元工厂,用来创建并管理Flyweight对象,主要是用来确保合理地共享Flyweight,当用户请求一个Flyweight时,FlyweightFactory对象提供一个已创建的实例或创建一个实例。
1 | public class FlyweightFactory { |
Client客户端
1 | public class Client { |
内部状态和外部状态
上面享元模式的定义为我们提出了两个要求:细粒度和共享对象。我们知道分配太多的对象到应用程序中将有损程序的性能,同时还容易造成内存溢出,要避免这种情况,用到的就是共享技术,这里就需要提到内部状态和外部状态了。
因为要求细粒度对象,所以不可避免地会使对象数量多且性质相近,此时我们就将这些对象的信息分为两个部分:内部状态和外部状态。
内部状态指对象共享出来的信息,存储在享元对象内部并且不会随环境的改变而改变;外部状态指对象得以依赖的一个标记,是随环境改变而改变的、不可共享的状态。
我们举一个最简单的例子,棋牌类游戏大家都有玩过吧,比如说说围棋和跳棋,它们都有大量的棋子对象,围棋和五子棋只有黑白两色,跳棋颜色略多一点,但也是不太变化的,所以棋子颜色就是棋子的内部状态;而各个棋子之间的差别就是位置的不同,我们落子嘛,落子颜色是定的,但位置是变化的,所以方位坐标就是棋子的外部状态。
那么为什么这里要用享元模式呢?可以想象一下,上面提到的棋类游戏的例子,比如围棋,理论上有361个空位可以放棋子,常规情况下每盘棋都有可能有两三百个棋子对象产生,因为内存空间有限,一台服务器很难支持更多的玩家玩围棋游戏,如果用享元模式来处理棋子,那么棋子对象就可以减少到只有两个实例,这样就很好的解决了对象的开销问题。
享元模式的应用
1. 何时使用
- 系统中有大量对象时
- 这些对象消耗大量内存时
- 这些对象的状态大部分可以外部化时
2. 方法
- 用唯一标识码判断,如果在内存中有,则返回这个唯一标识码所标识的对象,用HashMap/HashTable存储
3. 优点
- 大大减少了对象的创建,降低了程序内存的占用,提高效率
4. 缺点
- 提高了系统的复杂度。需要分离出内部状态和外部状态,而外部状态具有固化特性,不应该随着内部状态的改变而改变
- 为了节省内存,共享了内部状态,分离出外部状态,而读取外部状态 使运行时间变长。用时间换取了空间。
5. 使用场景
- 系统中存在大量相似对象
- 需要缓冲池的场景
6. 应用实例
- String常量池
- 数据库连接池
7. 注意事项
- 注意划分内部状态和外部状态,否则可能会引起线程安全问题
- 这些类必须有一个工厂类加以控制
结构型模式汇总
代理模式 | 为真实对象提供一个代理,从而控制对真实对象的访问 |
---|---|
适配模式 | 使原本由于接口不兼容不能一起工作的类可以一起工作 |
桥接模式 | 处理多层继承结构,处理多维度变化的场景,将各个维度设计成独立的继承结构,使各个维度可以独立的扩展在抽象层建立关联。 |
组合模式 | 将对象组合成树状结构以表示”部分和整体”层次结构,使得客户可以统一 的调用叶子对象和容器对象 |
装饰模式 | 动态地给一个对象添加额外的功能,比继承灵活 |
外观模式 | 为子系统提供统一的调用接口,使得子系统更加容易使用 |
享元模式 | 运用共享技术有效的实现管理大量细粒度对象,节省内存,提高效率 |
行为型模式
行为型模式关注的是系统中对象之间的相互交互,研究系统在运行时对象之间的相互通信和协作,进一步明确对象的职责。
创建型模式关注的是对象的创建过程。
结构型模式关注的是对象和类的组织。
十二、责任链模式(chain of responsibility)
使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链发送该请求,直到有一个对象处理它为止。
- Handler:抽象处理者。定义了一个处理请求的接口。
- ConcreteHandler:具体处理者。处理它所负责的请求,可访问它的后继者。如果可处理该请求就处理,否则就将该请求转发给它的后继者。
Implementation
抽象处理者
抽象处理者实现了三个职责:
- 定义一个请求的处理方法handlerMessage(),是唯一对外开放的方法
- 定义一个链的编排方式setNext(),用于设置下一个处理者
- 定义了具体的请求者必须实现的两个方法,即定义自己能够处理的级别的getHandlerLevel()方法及具体的处理任务echo()方法
1 | public abstract class Handler { |
具体处理者
这里我们定义三个具体处理者,以便能组成一条链,ConcreteHandlerB及ConcreteHandlerC就不再赘述了。
1 | public class ConcreteHandlerA extends Handler { |
责任链模式的应用
开发中常见的场景:
- Java中,异常机制就是一种责任链模式。一个try可以对应多个catch, 当第一个catch不匹配类型,则自动跳到第二个catch.
- Javascript语言中,事件的冒泡和捕获机制。Java语言中,事件的处理 采用观察者模式。
- Servlet开发中,过滤器的链式处理
- Struts2中,拦截器的调用也是典型的责任链模式
1. 何时使用
- 处理消息时
2. 方法
- 拦截的类都实现同一接口
3. 优点
- 将请求和处理分开,实现解耦,提高系统的灵活性
- 简化了对象,使对象不需要知道链的结构
4. 缺点
- 性能会收到影响,特别是在链比较长的时候
- 调试不方便。采用了类似递归的方式,调试时逻辑可能比较复杂
- 不能保证请求一定被接收
5. 使用场景
- 有多个对象可以处理同一个请求
- 在不明确指定接收者的情况下,向多个对象中的提交请求
- 可动态指定一组对象处理请求
6. 应用实例
- 多级请求
- 击鼓传花
- 请假/加薪请求
- Java Web中Tomcat对Encoding的处理、拦截器
7. 注意事项
- 需控制链中最大节点数量,一般通过在Handler中设置一个最大节点数量,在setNext()方法中判断是否已经超过阀值,超过则不允许该链建立,避免出现超长链无意识地破坏系统性能
十三、迭代器模式(iterator)
提供一种顺序访问聚合对象元素的方法,并且不暴露聚合对象的内部表示。
- Aggregate:聚集抽象类。负责提供创建具体迭代器角色的接口
- Iterator:迭代抽象类,用于定义得到开始对象、得到下一个对象、判断是否到结尾、当前对象等抽象方法,统一接口
- ConcreteAggregate:具体聚集类,继承Aggregate
- ConcreteIterator:具体迭代器类,继承Iterator,实现开始、下一个、是否结尾、当前对象等方法
Implementation
抽象容器
负责提供接口,比如存在一个类似createIterator()这样的方法,在Java中一般是iterator()方法。
1 | public interface Aggregate { |
抽象迭代器
负责定义访问和遍历元素的接口,基本上有固定的三个方法,即first()获取第一个元素、next()访问下一个元素、hasNext()是否已经遍历到底部。
1 | public interface Iterator { |
具体容器
1 | public class ConcreteAggregate implements Aggregate { |
具体迭代器
简单的实现就是通过一个游标,在一个容器中上下翻滚,遍历所有它需要查看的元素。
1 | public class ConcreteIterator implements Iterator { |
迭代器模式的应用
1. 何时使用
- 遍历一个聚合对象时
2. 方法
- 把在元素间游走的责任交给迭代器,而不是聚合对象
3. 优点
- 支持以不同的方式遍历一个聚合对象
- 迭代器简化了聚合类
- 在同一个聚合上可以有多个遍历
- 增加新的聚合类和迭代器类都很方便,无需修改原有代码
4. 缺点
- 增加了系统的复杂性。因为迭代器模式将存储数据和遍历数据的职责分离,增加了新的聚合类需要对应增加新的迭代器类,增加了系统的复杂性。
5. 使用场景
- 访问一个聚合对象的内容无需暴露它的内部表示时
- 需要为聚合对象提供多种便利方式时
- 为遍历不同的聚合结构提供一个统一的接口
6. 应用实例
- Java中的Iterator迭代器
- foreach遍历