[Java]设计模式

大话设计模式-常用设计模式总结

Posted by Stephen.Ri on May 24, 2020

通过封装、继承、多态把程序的耦合度降低;通过设计模式使程序更加灵活,容易修改,易于复用。

六大设计原则

设计原则 WHAT WHY HOW
单一职责 就一个类而言,应该仅有一个引起它变化的原因 如果一个类承担的指责过多,就等于把这些指责耦合在一起,一个指责的变化可能会削弱或者抑制这个类完成其他指责的能力。这种耦合会导致脆弱的设计,当变化发生时,设计会遭受意想不到的破坏 如果你能想到多于一个的动机去改变一个类,那么这个类就具有多于一个的指责
开放-封闭 对于扩展是开放的,对于更改是封闭的 为了让程序能面对需求的改变可以保持相对稳定,从而使系统在第一个版本后快速迭代。可维护、可扩展、可复用、灵活性好 最初编写代码时,假设变化不会发生。当变化发生时,创建抽象来隔离以后发生的同类变化。拒绝不成熟的抽象和抽象本身一样重要
里氏代换 子类型必须能够替换掉父类型 只有当子类可以替换掉父类,软件单位的功能不受到影响时,父类才能真正被复用,子类也能够在父类的基础上增加新的行为 继承
依赖倒转 抽象不应该依赖细节,细节应该依赖抽象 如果不管高层模块还是底层模块,都依赖于抽象(接口,或抽象类),只要接口是稳定的,那么任何一个更改都不用担心其他受到影响 针对接口编程。谁也不要依赖谁
迪米特 最少知识原则。如果两个类不必直接通信,那就不应当发生直接的相互作用 类之间的耦合越弱,越有利于复用,一个弱耦合的类被修改,不会波及到有关系的类 在类的结构设计上,每个类都应当尽量降低成员的访问权限
合成/聚合复用 | 聚合表示一种弱的‘拥有’关系,体现的是A对象可以包含B对象,但B对象不是A对象的一部分;合成则是一种强的‘拥有’关系,体现了严格的部分和整体的关系,部分和整体的生命周期一样 | 有助于保持每个类被封装,并被集中在单个任务上。这样类和类继承层次会保持较小规模,并且不太可能增长为不可控制的庞然大物 | 尽量使用合成/聚合,尽量不要使用类继承 |      

单例模式

类自身负责保存它的唯一实例。这个类可以保证没有其他实例可以被创建,并且它可以提供一个访问该实例的方法

单例模式

饿汉式

public class Singleton {  
    private static Singleton instance = new Singleton();  
    private Singleton (){}  
    public static Singleton getInstance() {  
        return instance;  
    }  
}

简单工厂模式

用一个单独的类来创造实例,这就是工厂

简单工厂模式

在用简单工厂的地方可以考虑用反射技术去除switch或if,解除分支判断带来的耦合

工厂方法模式

定义一个创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类

工厂方法模式

抽象工厂模式

提供一个创建一系列相关或相互依赖对象的接口,而无需指定他们具体的类。

抽象工厂模式

抽象工厂与简单工厂结合

可以用一个简单工厂类ProductAccess代替AbstractFactory, ConcreteFactory1, ConcreteFactory2三个类。简化客户端实现。

策略模式

策略模式是一种定义一系列算法的方法,从概念上来看,所有这些算法完成的都是相同的工作,只是实现不同,它可以以相同的方式调用所有的算法,减少了各种算法类与使用算法类之间的耦合

策略模式

策略模式与简单工厂结合

在基本的策略模式中,选择所用具体实现的职责由客户端对象承担,并转给策略模式的Context对象。这本身并没有解除客户端需要选择判断的压力,而策略模式与简单工厂模式结合后,选择具体实现的职责也可以由Context来承担,这就最大化地减轻了客户端的职责

状态模式

当一个对象的内在状态改变时,允许改变其行为,这个对象看起来是改变了其类。

状态模式

消除庞大的条件分支语句。

当一个对象的行为取决于它的状态,并且它必须在运行时刻根据状态改变它的行为时,就可以考虑使用状态模式了。

状态模式主要解决的是当控制一个对象状态转换的条件表达式过于复杂时的情况。把状态的判断逻辑转移到表示不同状态的一系列类当中,可以把复杂的判断逻辑简化。

建造者模式

将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示

如果我们使用了建造者模式,那么用户就只需指定需要建造的类型就可以得到他们,而具体建造的过程和细节就不需知道了

建造者模式

访问者模式

访问者模式讲的是表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作

访问者模式

适用于有比较稳定的数据结构,又有易于变化的算法的情况

观察者模式

又叫做发布—订阅模式(Publish/Subscribe),定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态发生变化时会通知所有观察者对象,使他们能够自动更新自己

观察者模式

当一个对象的改变需要同时改变其他对象,而且它不知道具体有多少对象有待改变时应该考虑观察者模式

观察者模式所做的工作其实就是在解除耦合。让耦合的双方都依赖于抽象,而不是依赖于具体

命令模式

将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤销的操作

请求模式

命令模式优点

  1. 能较容易地设计一个命令队列
  2. 在需要的情况下,可以容易地将命令记入日志
  3. 允许接受请求的一方决定是否要否决请求
  4. 可以容易地实现对请求的撤销和重做
  5. 加进新的具体命令类不影响其他的类,因此增加新的集体命令类很容易
  6. 把请求一个操作的对象与指导怎么执行一个操作的对象分割开

何时使用命令模式

敏捷开发原则告诉我们,不要为代码添加基于猜测的、实际不需要的功能。如果不清楚一个系统是否需要命令模式,一般就不需要着急去实现它,事实上,在需要的时候通过重构实现这个模式并不困难,只有在真正需要如撤销/恢复操作等功能时,把原来的代码重构为命令模式才有意义。

职责链模式

使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这个对象连成一条链,并沿着这条链传递该请求,直到一个对象处理它为止

职责链模式

当客户提交一个请求时,请求是沿链传递直到一个ConcreteHandler对象负责处理它。接收者和发送者都没有对方的明确信息,且链中的对象自己也并不知道链的结构。结果是职责链可简化对象的想到连接,它们仅需保持一个指向其后继者的引用,而不需保持它所有的候选接受者的引用,这样大大降低了耦合度。

代理模式

为其他对象提供一种代理以控制对这个对象的访问

代理模式

代理模式应用

  1. 远程代理:为一个对象在不同的地址空间提供局部代表。这样可以隐藏一个对象存在于不同地址空间的事实。
  2. 虚拟代理:根据需要创建开销很大的对象。通过它来存放实例化需要很长时间的真实对象。比如上面这张代理模式图片的加载。如果网络较慢,会有一个框代理图片。
  3. 安全代理:用于控制真实对象访问时的权限。
  4. 智能指引:当调用真实的对象时,代理处理另外一些事。

总之,代理模式其实就是在访问对象时引入一定程度的间接性,因为这种间接性,可以附加多种用途

适配器模式

将一个类的接口转换成客户希望的另外一个接口。Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。

适配器模式

适配器模式主要应用于希望复用一些现存的类,但是接口又与复用环境要求不一致的情况

组合模式

将对象组合成树形结构以表示‘部分-整体’的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性

组合模式

透明方式 vs 安全模式

  1. 透明方式:在Component中声明所有用来管理子对象的方法。好处是叶节点和枝节点对于外界没有区别,他们具备完全一致的行为接口。但问题也很明显,因为Leaf类本身不具备Add、Remove方法的功能,所以实现它是没有意义的。
  2. 安全方式:在Component接口中不去声明Add和Remove方法,而是在Composite声明所有用来管理子类对象的方法。由于不够透明,所以Leaf和Composite不具有相同的接口,客户端的调用需要做相应的判断,带来了不便。

原型模式

用原型实例指定创建对象的各类,并且通过拷贝这些原型创建新的对象。通俗讲就是从一个对象再创建另外一个可定制的对象,而且不需知道任何创建的细节

原型模式

Java中直接实现Cloneable接口即可

深复制 vs 浅复制

  1. 浅复制:被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用都仍然指向原来的对象
  2. 深复制:把引用对象的变量指向复制过的新对象,而不是原有的被引用的对象

常用深复制方法

  1. 让每个引用类型属性内部都重写clone()方法
  2. 利用序列化

迭代器模式

提供一种方法顺序地访问一个聚合对象中各个元素,而又不暴露该对象的内部表示

迭代器模式

迭代器模式就是分离了集合对象的遍历行为,抽象出一个迭代器中类来负责,这样既可以做到不暴露集合的内部结构,又可让外部代码透明地访问集合内部的数据

需要对聚焦对象有多种方法遍历时,可以考虑用迭代器模式

备忘录模式

在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将对象回复到原先保存的状态。

备忘录模式

把要保存的细节给封装在了Memento中,哪一天要更改保存的细节也不用影响客户端

外观模式

为子系统中的一组接口提供一个一致的界面,此模式定义了一个高层接口,这个接口使得这一子系统更加容易使用

外观模式

桥接模式

将抽象与它的实现部分分离,使它们都可以独立地变化。实现指的是抽象类和它的派生类用来实现自己的对象

桥接模式

实现系统可能有多角度分类,每一种分类都有可能变化,那么就把这种多角度分离出来让它们独立变化,减少它们之间的耦合

中介者模式

用一个中介对象来封装一系列的对象交互。中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变他们之间的交互

中介者模式

优点

  1. Mediator的出现减少了各个Colleague的后河,使得可以独立地改变和复用各个Colleague类和Mediator类
  2. 由于把对象如何协调进行了抽象,将中介作为一个独立的概念并将其封装在一个对象中,这样关注的对象就从对象各自本身的行为转移到他们之间的交互上来,也就是站在一个更宏观的角度去看待系统

缺点

由于ConcreteMediator控制了集中化,于是就把交互复杂性变为了中介者的复杂性,这就使得中介者会变得比任何一个ConcreteColleague都复杂。

应用

中介者模式一般应用于一组对象以定义良好但是复杂的方式进行通信的场合,以及想定制一个分布在多个类中的行为,而又不想生成太多的子类的场合

享元模式

运用享元技术有效支持大量细粒度的对象

享元模式

享元模式可以避免大量非常相似类的开销。FlyweightFactory根据客户需求返回早已生成好的对象。

解释器模式

给定一个语言,定义它在方法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子

解释器模式

当有一个语言需要解释执行,并且你可将该语言中的句子表示为一个抽象语法树时,可使用解释器模式

模式间对比

简单工厂 vs 工厂方法 vs 抽象工厂

简单工厂模式的工厂类包含了必要的逻辑判断,根据客户端的选择条件动态实例化相关的类。对于客户端来说,去除了与具体产品的依赖。

工厂方法模式把简单工厂的内部逻辑判断移到了客户端代码来进行。工厂方法克服了简单工厂违背开放-封闭原则的缺点,又保持了封装创建对象过程的优点。

抽象工厂模式易于交换产品系列。客户端通过抽象接口操纵实例,产品的具体类名也被具体工厂的实现分离。