开闭原则

一个软件实体应当对扩展开放,对修改关闭。 即软件实体应尽量在不修改原有代码的情况下进行扩展。

里氏代换原则

所有引用基类( 父类) 的地方必须能透明地使用其子类的对象。

依赖倒转原则

依赖倒转原则要求我们在程序代码中传递参数时或在关联关系中, 尽量引用层次高的抽象层类, 即使用接口和抽象类进行变量类型声明、 参数类型声明、 方法返回类型声明,以及数据类型的转换等, 而不要用具体类来做这些事情。

接口隔离原则

使用多个专门的接口,而不是使用单一的总接口

合成复用原则

尽量使用对象组合,而不是继承来达到复用的目的。

迪米特法则

不要和陌生人说话,只与你的朋友通讯。
朋友:1.当前对象本身(this)
2.以参数形式传入到当前对象方法中的对象
3.当前对象的成员对象
4.如果当前对象的成员对象是一个集合,那么集合中的元素也都是朋友
5.当前对象所创建的对象

创建型模式

简单工厂模式 - Simple Factory Pattern

简单工厂模式提供了专门的工厂类用于创建对象,将对象的创建和对象的使用分离开。
image.png
优点
工厂类中包含必要的判断逻辑,客户端可以免除直接创建产品对象的职责
客户端无需知道所创建的具体产品类的类名,只需要知道具体产品类所对应的参数即可,对于一些复杂的
类名,通过简单工厂模式可以在一定程度减少使用者的记忆量。
缺点
由于工厂类集中了所有产品逻辑,一旦不能正常工作,整个系统都会受影响
使用简单工厂模式会增加系统中类的个数,增加了系统的复杂度和理解难度
系统拓展困难,一旦添加新产品就不得不修改工厂逻辑,在产品类型较多时,有可能造成工厂逻辑过于复杂,不利于系统拓展和维护
简单工厂模式由于使用了静态工厂方法,造成工厂角色无法形成基于继承的等级结构
适用场景
工厂类负责创建的对象比较少,由于创建的对象比较少,不会造成工厂方法中的业务逻辑太过复杂
客户端不关心对象如何创建

工厂方法模式 Factory Method Pattern

image.png
总结
工厂方法是简单工厂模式的眼神,继承了简单工厂模式的有点,还弥补了简单工厂模式的不足。
优点
隐藏了产品类实例化的细节,在系统中加入新产品时无需修改抽线工厂和抽象产品提供的接口,无需修改客户端,只需要添加一个具体工厂和产品接口,系统拓展性变得非常好
缺点
添加新产品时,需要编写具体产品类,还需要提供与之对应的具体工厂类,增加了系统的复杂度;需要引入抽象层,增加了系统的抽象性和理解难度,实现时可能用到DOM、反射等技术,增加了系统的实现难度。
适用场景
客户端不知道他所需要的对象的类,只需要知道对应的工厂即可,具体的产品对象由具体工厂类创建;

抽象工厂方法 Abstract Factory Pattern

image.png
是工厂方法模式的进一步延伸,是软件开发中最常用的设计模式之一
优点
隔离了具体类的生成,使得客户并不需要知道什么被创建
缺点
增加心得产品等级结构麻烦,甚至需要修改抽象层代码,违背了“开闭原则”
适用场景
一个系统不应该依赖于产品类的实例如何被创建、组合和表达的细节,用户无需关心对象的创建过程,将对象的创建和使用解耦
属于同一个产品组的产品将在一起使用
单例模式 Singleton Pattern
饿汉式单例与懒汉式单例
饿汉

  1. class EagerSingleton {
  2. private static final EagerSingleton instance = new EagerSingleton();
  3. private EagerSingleton() {}
  4. public static EagerSingleton getInstance(){
  5. return instance;
  6. }
  7. }

懒汉

  1. class LazySingleton {
  2. private static LazySingleton instance = null;
  3. private LazySingleton {}
  4. synchronized public static LazySingleton getInstance() {
  5. if (instance == null) {
  6. synchronized(LazySingleton.class) {
  7. if(instance = null) {
  8. instance = new LazySingleton();
  9. }
  10. }
  11. }
  12. return instance;
  13. }
  14. }

饿汉与懒汉之间的区别
饿汉式在被加载时就将自己实例化,不需要考虑多线程访问的问题,可以确保实例的唯一性;
懒汉式在第一次使用时候再加载,无须一直占用系统资源,实现了延迟加载。但需要考虑多线程下的线程安全问题。
一种更好的单例实现方法

  1. class Singleton {
  2. private Singleton() {}
  3. private static class HolderClass {
  4. private final static Singleton instance = new Singleton();
  5. }
  6. public static Singleton getInstance() {
  7. return HolderClass.instance;
  8. }
  9. }

总结
单例模式在软件开发中使用频率相当高
优点
提供了唯一实例的受控访问,单例封装了唯一实例;在系统内存只存在一个对象,因此可以解决系统资源,对于一些频繁创建和销毁的对象单例模式可以提高系统的性能;
缺点
单例模式中没有抽象层使得单例类的拓展有很大的困难;单例类的职责过重,在一定程度上违背了“单一职责原则”;如果实例化的对象长时间不被利用,系统会认为它是垃圾,会自动销毁并回收资源,下次利用时又会重新实例化,导致共享单例对象的状态丢失。
适用场景
系统只需要一个实例对象

原型模式 Prototype Pattern

image.png
原型模式工作原理:将一个原型对象传给那个要发动创建的对象,这个要发动创建的对象通过请求原型对象拷贝自己来显示创建过程。(软件系统中经常会遇到需要创建多个相同或者相似对象的情况)。克隆方法所创建的对象是全新的对象,每一个克隆对象都是相互独立的。
总结
原型模式作为一种快速创建大量相同或相似对象的方式在软件开发中应用较为广泛
优点
当创建新的对象实例较为复杂时,使用原型模式可以简化对象的创建过程,通过复制一个已有实例可以提高新实例的创建效率;扩展性较好,简化的创建结构,无需专门工厂类来创建产品;可以使用深克隆的方式保存对象状态,使用原型模式将对象复制一份并将其保存起来,可辅助实现撤销操作
缺点
违背“开闭原则”,对象多重嵌套引用时,为了实现深克隆每一次对象对应额类都必须要支持深克隆,实现起来比较麻烦
适用场景
创建新对象成本较大;系统要保存对象状态(本身占用内存较小);避免适用分层次的工厂类来创建分层次的对象

建造者模式 Builder Pattern

image.png
省略Director,将Director和抽象建造者Builder合并,可以将constructor()方法定义为静态(static)方法。

  1. abstract class ActorBuilder {
  2. protected static Actor actor = new Actor();
  3. public abstract void buildType();
  4. public abstract void buildSex();
  5. public abstract void buildFace();
  6. public abstract void buildCostume();
  7. public abstract void buildHairstyle();
  8. public static Actor construct(ActorBuilder ab) {
  9. ab.buildType();
  10. ab.buildSex();
  11. ab.buildFace();
  12. ab.bnuildCostume();
  13. ab.buildHairstyle();
  14. return actor;
  15. }
  16. }
  1. ...
  2. ActorBuilder ab;
  3. ab = (ActorBuilder)XMLUtil.getBean();
  4. Actor actor;
  5. actor = ActorBuilder.construct(ab);
  6. ...

钩子方法引入
定义抽象建造者类中可以提供一个默认实现
总结
建造者模式核心在于如果一步步构建一个包含多个组成部件的完整对象,使用相同的构建过程构建不同的产品。
优点
建造者模式中,客户端不必知道内部组成的细节;每一个具体建造者都相对独立,符合“开闭原则”;可以更加精细地控制产品的创建过程。
缺点
如果产品之间的差异性很大,则不适合使用建造者模式;如果产品内部变化复杂,可能会使系统变得很庞大,增加系统的理解成都和运行成本。
适用场景
需要生成的产品对象有复杂的内部结构,产品对象通常包含多个成员属性;产品对象属性相互依赖,需要指定其生成顺序;对象的创建过程独立于创建该对象的类

结构型模式

适配器模式 Adapter Pattern

image.png
将一个接口转换成客户希望的另一个接口,使接口不兼容的那些类可以一起工作,别名为包装器(Wrapper)

  1. class Adapter extends Target {
  2. private Adaptee adaptee; // 维持一个对适配者对象的引用
  3. public Adapter(Adaptee adptee) {
  4. this.adaptee = adaptee;
  5. }
  6. public void request() {
  7. adaptee.specificRequest();//转发调用
  8. }
  9. }

引用适配器、类适配器、双向适配器

  1. class Adapter implements Target, Adaptee {
  2. // 同时维持对目标类和适配者的引用
  3. private Target target;
  4. private Adaptee adaptee;
  5. ...
  6. }

总结
适配器模式将现有接口转化为客户类所期望的接口,实现了对现有类的服用。
优点
目标类和适配者类解耦;增加了类的透明性和复用性;灵活性和拓展性都非常好,完全符合“开闭原则”
缺点
与适配器模式相比,要在适配器中置换适配者类的某些方法比较麻烦。
适用场景
系统需要使用一些现有的类,而这些类的接口不符合系统的需要;想创建一个可以重复使用的类,用于一些彼此之间没有太大关联的类

桥接模式 Bridge Pattern

image.png
处理多维度变化
如果软件系统中某个类存在两个独立变化的维度,则可以通过该模式让两个维度分离出来,使两者可以独立扩展。
将抽象部分与它的实现部分分离,使它们可以独立地变化。先识别出一个类所具有的两个独立变化的维度,将它们设计为两个独立的继承等级结构,为两个维度都提供抽象层,并建立耦合抽象。
桥接模式经常和适配器模式一起搭配使用;桥接模式是设计Java虚拟机和实现JDBC等驱动程序的核心模式之一
优点
分离抽象接口及其实现部分;桥接模式在很多情况下都可以取代多层继承方案;提高系统的可拓展性
缺点
增加系统的理解与设计难度,需要开发者一开始就针对抽象层进行设计与编程;
适用场景
一个系统需要在抽象化和具体化之间增加更多的灵活性,避免在两个层次之间建立静态的继承关系
不系统使用继承或因多层继承导致系统类个数急剧增加的系统

组合模式 Composite Pattern

image.png
组合模式组合多个对象形成树形结构以表示具有“整体-部分”关系的层次结构。
总结
组合模式使用面向对象的思想来实现树形结构的构建与处理,描述了如何将容器对象和叶子对象进行递归组合,实现简单,灵活性好。
优点
组合模式可以清楚地定义分层次的复杂对象,让客户端忽略了层次的差异;客户端可以一致地使用结构或单个对象;符合开闭原则;为树形结构提供了一种灵活的解决方案
缺点
新增构件时很难对容器中的构件类型进行限制,例如某个文件夹中只能包含文本文件,使用组合时不能依赖类型系统来施加这些约束,必须在运行时进行类型检查来实现,过程较为复杂
适用场景
具有整体和部分的层次结构中,希望通过一种方式忽略整体与部分的差异;需要处理一个树形结构;系统中能够分离出叶子对象和容器对象

装饰模式 Decorator Pattern

image.png
装饰模式是一种用于替代继承的技术,通过一种无需定义子类的方式来个对象动态增加职责。

  1. class Decorator implements Component {
  2. private Component component; //维持一个对抽象构件对象的引用
  3. public Decorator(Component component) {
  4. this.component = component;
  5. }
  6. public void operation() {
  7. componnet.operation(); //调用原有业务方法
  8. }
  9. }
  10. class ConcreteDecorator extends Decorator {
  11. public ConcreteDecorator(Component component) {
  12. super(component);
  13. }
  14. public void operation() {
  15. super.operation(); //调用原有业务方法
  16. addedBehavior(); //调用新增业务方法
  17. }
  18. public void addedBehavior() {
  19. //... 新增业务方法
  20. }
  21. }

透明装饰模式/半透明装饰模式
透明装饰模式中,要求客户端完全针对抽象编程,并且全部声明为具体构件类型或具体装饰类型;对于客户端而言,具体构件对象和具体装饰对象没有任何区别;在实现透明装饰时,要求具体装饰类的operation()方法覆盖抽象装饰类的operation()方法,除了调用原有对象的operation()外还需要调用新增的addedBehavior()方法来增加新行为。
半透明装饰模式
为了能够调用到新增方法,不得不具体装饰类型来定义装饰后的对象,而具体构件类型还是可以使用抽象构件类型来定义,这种装饰模式即为半透明装饰模式。

  1. ...
  2. Document doc; //使用抽象构件定义
  3. doc = new PurchaseRequest();
  4. Approve newDoc; // 使用具体装饰类型定义
  5. newDoc = new Approve(doc);
  6. ...

半透明模式可以给系统带来更多的灵活性,设计相对简单,使用起来也方便;但无法实现对同一对象的多次装饰,而客户端需要有区别地对待装饰之前的对象和装饰之后的对象。
注意事项
尽量保持装饰类的接口与被装饰类的接口相同,这样,无论是装饰之前的对象还是装饰之后的对象都可以一致对待。尽量使用透明装饰模式;尽量保持具体构件类ConcreteComponent是一个“轻”类;如果只有一个具体构建类,那么抽象装饰类可以直接作为该具体构建类的直接子类
总结
装饰模式降低了系统的耦合度,可以动态增加或删减对象的职责,以便增加新的具体构建类和具体装饰类。
优点
对于扩展一个对象的功能,装饰模式比继承更加灵活,不会导致类的个数急剧增加;通过动态的方式来扩展对象的共鞥;可以对一个对象进行多次装饰;符合开闭原则
缺点
会产生很多小对象,一定程度上影响程序性能;灵活性导致比继承更加易于出错,比较难排错
适用场景
在不影响其他对象的情况,以动态 透明的方式给单个对象添加职责;不能采用继承的方式对系统进行扩展或采用继承不利于扩展和维护时可以使用装饰模式

外观模式 Facade Pattern

image.png
外观模式通过引入一个新的外观类(Facade)来实现该功能,它为多个业务类的调用提供了统一的一个入口,简化了类之间的交互。
外观模式的目的主要降低系统的复杂度,外观模式时一种使用频率非常高的设计模式,通过引入一个外观角色来简化客户端与子系统之间的交互,为复杂的子系统提供一个统一的入口,使子系统与客户端的耦合度降低。
优点
屏蔽了子系统组件;实现了子系统与客户端之间的松耦合关系
缺点
如果设计不当则违背开闭原则;不能很好地限制客户端直接使用子类
适用场景
为访问一系列复杂的子系统提供一个简单的入口可以使用外观模式;客户端程序与多个子系统之间存在很大的依赖性。

享元模式 Flyweight Pattern

image.png
享元模式以共享的方式高效地支持大量细粒度对象的重用,享元对象能做到共享的关键是区分了内部状态(Inrinsic State)和外部状态(Extrinsic State)
内部状态是存储在享元对象内部并且不会随环境变化而改变的状态,内部状态可以共享。
外部状态是随环境改变而改变的,不可以共享的状态。享元对象的外部状态通常由客户端保存,并在享元对象被创建之后,需要使用的时候再传入到享元对象内部。一个外部状态与另一个外部状态之间是相互独立的。如字符颜色,字符的大小。

  1. class Coordinates {
  2. private int x;
  3. private int y;
  4. public Coordinates(int x, int y) {
  5. this.x = x;
  6. this.y = y;
  7. }
  8. }
  9. class IgoChessmanFactory {
  10. private static IgoChessmanFactory instance = new IgoChessmanFactory();
  11. private static Hashtable ht;
  12. private IgoChessmanFactory() {
  13. ht = new HashTable();
  14. IgoChessman black, white;
  15. black = new BlackIgoChessman();
  16. ht.put("b", black);
  17. white = new WhiteIgoChessman();
  18. ht.put("w", white);
  19. }
  20. public staic IgoChessmanFactory getInstance() {
  21. return instance;
  22. }
  23. public static IgoChessman getIgoChessman(String color) {
  24. return (IgoChessman)ht.get(color);
  25. }
  26. }
  27. abstract class IgoChessman {
  28. public abstract String getColor();
  29. public void display(Coordinates coord) {
  30. // sout
  31. }
  32. }
  33. class Client {
  34. public void static main(String args[]) {
  35. IgoChessman black1, black2, black3, white1, white2;
  36. IgoChessmanFactory facotry;
  37. factory = IgoChessmanFactory.getInstance();
  38. black1 = factory.getIgoChessman("b");
  39. black2 = factory.getIgoChessman("b");
  40. black3 = factory.getIgoChessman("b");
  41. white1 = factory.getIgoChessman("w");
  42. white2 = factory.getIgoChessman("w");
  43. black1.display(new Coordinates(1,2));
  44. black2.display(new Coordinates(3,4));
  45. }
  46. }

单纯享元模式和复合享元模式
单纯享元模式所有的具体享元类都可以共享,不存在非共享具体享元类;
复合享元类:通过复合享元模式,可以确保复合享元类中包含的每个单纯享元类都具有相同的外部状态,而这些单纯享元的内部状态往往可以不同。
总结
当系统中存在大量相同或相似对象时,享元模式是一种较好的解决方案,它通过共享技术实现相同或相似的细粒度对象复用从而节约了内存空间。
优点
可以极大减少内存中对象的数量,使得相同或相似对象在内存中只保存一份,从而节约系统资源;享元模式的外部状态相对独立而且不会影响其内部状态。
缺点
使系统变得复杂,需要分离出内部状态和外部状态;读取外部状态使得运行时间边长
使用享元模式时需要维护一个储存享元对象的享元池,而这需要耗费一定的系统资源,因此 需要多次重复使用享元对象时才值得使用享元模式。

代理模式 Proxy Pattern

image.png
代理模式是常用的结构性设计模式之一,当无法直接访问某个对象或访问某个对象存在困难时可以通过一个代理对象来间接访问。
代理模式是一种应用很广泛的结构型设计模式,而且变化形式非常多。远程代理、虚拟代理、保护代理、缓冲代理、智能引用代理。
远程代理:使客户端程序可以访问在远程主机上的对象,远程主机可能具有更好的计算性能和处理速度。
虚拟代理:对于一些占用系统资源较多或者加载时间较长的对象,可以给这些对象提供一个虚拟代理。在真实对象创建成功之前虚拟代理扮演真是对象的替身,而当真是对象创建之后,虚拟代理将用户请求转发给真实对象。
适用范围:
由于对象本身的复杂性或者网络等原因导致一个对象需要较长的加载时间;
当一个对象加载十分耗费系统资源的时候,也非常适合使用虚拟代理
缓冲代理:为某一个操作的结果提供临时的缓存存储空间,以便在后续使用中能够共享这些结果,从而避免某些方法重复执行。
优点
降低耦合度;复合开闭原则,有较好的灵活性和可扩展性
缺点
某些代理模式可能请求变慢;某些代理模式实现较为复杂
适用场景
需要远程访问主机中的对象;需要用一个小号资源较少的对象来代表一个消耗较多的对象

行为模式

责任链模式 Chain of Responsibility Pattern

image.png
责任链模式定义:避免请求发送者与接收者耦合在一起,让多个对象都可能接受请求,将这些对象练成一条链,并且沿着这条链传递请求,直到有对象处理它为止。
纯与不纯的职责链模式
纯的职责链模式:具体处理者只能在两个行为中选择一个:要么承担全部责任,要么将责任推给下家。
不纯的职责链模式:允许某个请求被一个具体处理者部分处理后再向下传递,或者一个具体处理者处理完请求后其后记处理者可以继续处理该请求。
总结
职责链通过建立一条链来组织请求的处理者,实现了发送者与处理者的解耦。
优点
降低系统耦合度;可以简化对象的相互连接;可通过运行时对该链进行动态的增加或修改来增加或改变处理一个请求的职责;增加新的具体请求者处理者,无需修改原有的系统代码,复合开闭原则。
缺点
没有明确的接收者,导致不一定会被处理;系统性能受影响,调试不变;如果建链不当,会造成死循环
适用场景
有多个对象可以处理同一个请求;不明确指定接受的情况下,向多个对象中一个提交一个请求;可动态指定一组对象处理请求,客户端可以动态创建职责链来处理请求,还可以改变链中处理者的先后次序。

命令模式 Command Pattern

image.png
我们经常需要向某些对象发送请求,但是并不知道请求的接受者是谁。命令模式可以将请求发送者和请求的接收者完全解耦,发送者与接收者之间没有直接引用关系,发送请求的对象只需要知道如何发送请求,而不必知道如何完成请求。
命令模式是一种使用频率非常高的设计模式,它可以将请求发送者与接收者解耦,使得系统具有更好的灵活性和可扩展性。
优点
降低系统耦合度,请求者与接收者之间完全解耦;新命令可以很容易加到系统中,满足开闭原则;可以比较容易设计一个命令队列;为请求的撤销(Undo)和恢复(Redo)提供了一种设计方案
缺点
命令模式可能会导致某些系统有过多的具体命令类,因为针对每一个请求接收者的调用操作都需要设计一个具体的命令类。
适用场景
系统需要将请求调用者和请求接收者解耦,使得调用者和接收者不直接交互;系统需要在不同的时间指定请求、将请求排队和执行请求;系统需要支持命令的撤销操作和恢复操作;系统将一组操作组合在一起形成宏命令

*解释器模式 Interpreter Pattern

image.png
解释器模式描述了如何为简单的语法定义一个文法,如何在该语言中标识一个句子,以及如何解释这些句子。在解释器模式中还可以通过一种称之为抽象语法树的图形方式来直观地表示语言的构成。
解释器模式:定义一个语言的文法,并且建立一个解释器来解释该语言中的句子。
解释器模式为自定义语言的设计和实现提供了一种解决方案。
优点
易于改变和扩展文法;可以方便地实现一个简单的语言;增加新的解释表达式比较方便,原有表达式无需修改,符合开闭原则
缺点
对于复杂文法难以维护;执行效率低
适用场景
可以将一个需要解释执行的语言中的句子表示为一个抽象语法树;一些重复出现的问题可以用一种简单的语言来表达;执行效率不是关键问题

迭代器模式 Iterator Pattern

image.png
迭代器模式:提供一种方法来访问聚合对象,而不用暴露这个对象内部标识,其别名为游标。
总结
迭代器模式是一种使用频率非常高的设计模式,通过引入迭代器可以将数据的遍历功能从聚合对象中分离出来。
优点
支持不同方式遍历一个对象;迭代器简化了聚合类;引入了抽象层,增加新的集合类和迭代器类都很方便,无需修改原有代码,满足开闭原则。
缺点
增加新的聚合类需要对应增加新的迭代器类,类的个数成对增加,一定程度上增加了系统的复杂性
适用场景
访问一个聚合类对象的内容无需暴露它的内部表示;需要为一个聚合对象提供多种遍历方式;为遍历不同的聚合结构提供一个统一的接口。

中介者模式 Mediator Pattern

image.png
如果一个系统中对象之间的关系呈现为网状结构,对象之间存在大量的多对多关系,将导致系统非常复杂。如果一个系统中对象之间存在多对多的相互关系,我们可以将对象之间的一些交互行为从各个对象中分离出来,并集中封装在一个中介者对象中,并由该中介者进行统一协调。
总结
中介者模式将一个网状结构变成一个以中介者对象为中心的星形结构。
优点
简化了对象之间的交互,用中介者和同事的一对多交互替代了原来同事之间的多对多交互;中介者利于各同事之间的松耦合,更符合开闭原则;减少子类的生成,将原本分布于多个对象间的行为集中在一起。
缺点
中介类中包含了大量同事之间的交互细节,可能会导致具体中介者类非常复杂,使得系统难以维护。
适用场景
系统中对象间存在复杂的引用关系,系统混乱且难以理解;一个对象由于引用了其他很多对象并直接和对象通讯导致难以复用;通过一个中间类来封装多个类中的行为,如果需要改变行为则可以增加新的具体中介类

备忘录模式 Memento Pattern

image.png
备忘录模式正为解决撤销类问题而诞生
备忘录模式提供了一种状态恢复的实现机制,使得用户可以方便地回到一个特定的历史步骤。
总结
提供了一种状态恢复的实现机制,使得用户可以方便地回到一个特定的历史步骤,当新的状态或者存在问题时,可以使用暂时存储起来的备忘录恢复状态;实现了对信息的封装
缺点
资源消耗过大

观察者模式 Observer Pattern

image.png
观察者模式是使用频率最高的设计模式之一,它用于建立一种对象与对象之间的依赖关系,一个对象发生改变时自动通知其他对象,其他对象将相应地做出反应。观察者模式的别名包括发布-订阅模式、模型-试图模式、源-监听器模式或从属者模式。观察者模式是一种对象行为模式。
JDK对观察者模式的支持
观察者模式在Java语言中的地位非常重要,在JDK的java.util包中,提供了Observable类遗迹Observer接口,它们构成了JDK对观察者模式的支持。我们可以直接使用Observer接口和Observable类来作为观察者模式的抽象层,再自定义具体观察者类和具体观察目标类。
总结
观察者模式是一种使用频率非常高的设计模式,它为实现对象之间的联动提供了一套完整的解决方案,凡是涉及到一对一或者一对多的对象交互场景都可以使用观察者模式。
优点
可以实现表示层和数据逻辑层的分离,定义了稳定的消息更新传递机制,并抽象了更新接口;支持广播同喜;满足“开闭原则”
缺点
观察者和观察目标之间存在循环依赖;不知道对象时如何发生变化的
适用场景
一个抽象模型有两个方面,其中一个方面依赖于另一个方面,将这两个方面封装在独立的对象中使它们可以各自独立地改变和复用;一个对象的改变将导致一个或多个其他对象也发生改变,而并不知道有多少对象将发生改变;需要在系统中创建一个触发链,A对象行为影响B对象,B对象行为影响C对象

状态模式 State Pattern

image.png
在状态模式中,我们将对象在每一个状态下的行为和状态转移语句封装在一个个状态中,通过这些状态来分散冗长的条件转移语句,让系统具有更好的灵活性和可扩展性。
有些情况下,多个环境对象可能需要共享一个状态,如果系统在系统中实现多个环境对象共享一个或多个状态对象,那么需要将这些状态对象定义为环境类的静态成员对象。
在状态模式中实现状态转换时,具体状态类可通过调用环境类Context的setState()方法进行状态的转换操作,也可以统一由环境类Context来实现状态的转换。
总结
状态模式将一个对象不同状态下的不同行为封装在一个个状态类中,通过设置不同的状态对象可以让环境对象拥有不同的行为,而状态转换的细节对于客户端是透明的。在实际开发中,具有较高的使用频率,在工作流和游戏开发中都得到了广泛的应用。例如:公文状态的转换、游戏中角色的升级等。
优点
封装了状态的转换规则,在状态模式中可以将状态的转换代码封装到环境类或者具体状态类中,可以对转换代码集中管理;将所有与某个状态有关的类放到一个类中,只需要注入一个不同的状态对象即可使环境对象拥有不同的行为;允许状态转换逻辑与状态对象合为一体,而不是提供一个巨大的条件语句块,状态模式可以让我们避免适用庞大的条件语句将业务方法和状态转换代码交织在一起。;可以让多个环境对象共享一个状态对象,从而减少系统中对象的个数。
缺点
会增加系统中类和对象的个数,导致系统运行开销增大;结构与实现都较为复杂;使用不当导致程序结构和代码混乱;不太符合“开闭原则”
适用场景
对象的行为依赖于它的状态,状态的改变导致行为的变化;在代码中包含大量与对象状态有关的条件语句,这些语句都会导致代码的可维护性和灵活性变差,导致客户类与类库之间耦合增强。

策略模式 Strategy Pattern

image.png
策略模式主要目的是将算法的定义与使用分开,也就是将算法和环境分开。
总结
策略模式用于算法的自有切换和扩展,它是应用较为广泛的设计模式之一。策略模式对于解决某一问题的一个算法族,允许用户从算法族中任选一个算法来解决某一问题,同时可以方便地更换算法或增加新的算法。只要涉及到算法的封装、复用、切换都可以考虑使用策略模式。
优点
“开闭原则”完美支持;提供管理相关算法族的办法,可以避免重复代码;提供了一种可以替换继承关系的办法;避免多重条件选择语句;不同环境可以复用策略
缺点
客户端必须知道所有策略类;需要生成具体策略类;无法使用多个策略类
适用场景
动态地在几种算法中选择一种;一个对象可有很多行为;不系统客户端知道复杂的算法

模板方法模式 Template Method Pattern

image.png
模板方法是结构最简单的行为型设计模式,在其结构中只存在于父类与子类之间的继承关系。通过使用模板方法模式,可以将一些复杂流程的实现步骤封装在一系列的基本方法中,在抽象父类中提供一个称之为模板方法的方法来定义这些基本方法执行次序。由于面向对象的多态性,子类对象在运行时覆盖父类对象,子类中定义的方法也将覆盖父类中定义的方法,因此程序在运行时,具体子类的方法将覆盖父类中定义的基本方法,子类的钩子方法也将覆盖父类的钩子方法,从而可以通过在子类中实现的钩子方法对父类方法的执行进行约束,实现子类对父类的反向控制。
总结
模板方法是基于继承的代码复用技术,它体现了面向对象的诸多重要思想,是一种使用较为频繁的模式。模板方法模式广泛应用于框架设计中,以确保通过父类来控制处理流程的逻辑顺序。
优点
父类定义一个算法,由子类来实现细节的处理;反向控制结构,通过子类覆盖父类的狗子方法来决定某一特定步骤是否需要执行;通过子类来覆盖父类的基本方法,符合单一职责原则与开闭原则
缺点
需要为每一个基本本方法的不同实现提供一个子类,如果父类中可变的基本方法太多,会导致类的个数增加,系统更加庞大,设计也更加抽象。
适用场景
对于一些复杂算法进行切割;子类的公共行为可以提取到父类中 避免代码重复;通过子类来决定父类算法中某个步骤是否执行,实现子类对父类的反向控制。

访问者模式 Visitor Pattern

image.png
访问者模式:提供一个作用于某对象结构中的各元素的操作表示,它使我们可以在不改变各元素的类的前提下定义作用于这些元素的新操作。
优点
增加新的访问操作很方便;将有关元素对象的访问行为集中到一个访问者对象中,而不是分散在一个个的元素类中。
缺点
增加元素类很困难;破坏封装
适用场景
一个对象结构包含多个类型的对象;需要对一个对象结构中的对象进行很多不同的并且不相关的操作;对象结构中对象对应的类很少改变