目录
本文引用了大量菜鸟教程的设计模式教程;原地址:https://www.runoob.com/design-pattern/design-pattern-tutorial.html
一、面向对象设计五个基本原则(SOLID)
1.单一职责原则(SRP:Single Responsibility Principle)
一个类或者模块应该有且只有一个改变的原因。
2.开闭原则(Open Closed Principle)
一个实体是允许在不改变它的源代码的前提下变更它的行为。(用继承来解决)
3.接口隔离原则(ISP:Interface Segregation Principle)
使用多个专门的接口比使用单一的总接口要好。
一个类对另外一个类的依赖性应当是建立在最小的接口上的。
4.里氏替换原则(Liskov Substitution principle)
在面向对象程序设计(OOP:Object-oriented programming)中,里氏替换原则是对子类型的特别定义。
“派生类(子类)对象可以在程式中代替其基类(超类)对象。” (多态)
5.依赖倒置原则(Dependence Inversion Principle)
程序要依赖于抽象接口,不要依赖于具体实现。(抽象类诞生,降低耦合度)
A.高层次的模块不应该依赖于低层次的模块,他们都应该依赖于抽象。
B.抽象不应该依赖于具体,具体应该依赖于抽象。
二、什么是设计模式(Design pattern)?
设计模式(Design pattern)代表了最佳的实践。
计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案。这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的。
总结:设计模式是代码设计经验的总结。
三、设计模式的类型
创建型模式(隐藏创建对象的逻辑,让使用者不通过new运算符直接实例化对象)
- 工厂模式(Factory Pattern)
- 抽象工厂模式(Abstract Factory Pattern)
- 单例模式(Singleton Pattern)
- 建造者模式(Builder Pattern)
- 原型模式(Prototype Pattern)
结构型模式(关注类和对象的组合,通过继承来组合接口和定义组合对象以此获得新功能的方式)
- 适配器模式(Adapter Pattern)
- 桥接模式(Bridge Pattern)
- 过滤器模式(Filter、Criteria Pattern)
- 组合模式(Composite Pattern)
- 装饰器模式(Decorator Pattern)
- 外观模式(Facade Pattern)
- 享元模式(Flyweight Pattern)
- 代理模式(Proxy Pattern)
行为型模式(关注对象之间的通信)
- 责任链模式(Chain of Responsibility Pattern)
- 命令模式(Command Pattern)
- 解释器模式(Interpreter Pattern)
- 迭代器模式(Iterator Pattern)
- 中介者模式(Mediator Pattern)
- 备忘录模式(Memento Pattern)
- 观察者模式(Observer Pattern)
- 状态模式(State Pattern)
- 空对象模式(Null Object Pattern)
- 策略模式(Strategy Pattern)
- 模板模式(Template Pattern)
- 访问者模式(Visitor Pattern)
J2EE 模式(关注表示层,在Java开发中产生的)
- MVC 模式(MVC Pattern)
- 业务代表模式(Business Delegate Pattern)
- 组合实体模式(Composite Entity Pattern)
- 数据访问对象模式(Data Access Object Pattern)
- 前端控制器模式(Front Controller Pattern)
- 拦截过滤器模式(Intercepting Filter Pattern)
- 服务定位器模式(Service Locator Pattern)
- 传输对象模式(Transfer Object Pattern)
四、设计模式之间的关系
五、设计模式的六大原则
1、开闭原则(Open Close Principle)
对扩展开放,对修改关闭。
在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果,使程序的扩展性更好,更易于维护和升级。为了达到这样的效果,我们需要使用接口和抽象类。
2、里氏代换原则(Liskov Substitution Principle)
里氏代换原则是对开闭原则的补充。
实现开闭原则的关键步骤就是抽象化,而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。
3、依赖倒转原则(Dependence Inversion Principle)
依赖倒转原则是开闭原则的基础。
具体内容:针对接口编程,依赖于抽象而不依赖于具体。
4、接口隔离原则(Interface Segregation Principle)
使用多个隔离的接口,比使用单个接口要好,降低类之间的耦合度。
强调降低依赖,降低耦合。
5、迪米特法则,又称最少知道原则(Demeter Principle)
一个实体应当尽量少地与其他实体之间发生相互作用,使得系统功能模块相对独立。
6、合成复用原则(Composite Reuse Principle)
尽量使用合成/聚合的方式,而不是使用继承。
六、工厂模式
1、定义需要创建对象的类的接口,所有可以被创建的对象类实现该接口;
2、定义一个工厂类,该工厂类中存在一个方法通过就收字符串来返回以该字符串为名字的类的实例。
主要解决:接口选择的问题。
interface Y {
void go();
}
class A implements Y {
@Override
public void go() {
System.out.println("A go!");
}
}
class B implements Y {
@Override
public void go() {
System.out.println("B go!");
}
}
public class Factory {
public Y getInstance(String className) {
if (className == null)
return null;
if (className.equals("A"))
return new A();
if (className.equals("B"))
return new B();
return null;
}
}
七、抽象工厂模式
工厂类继承一个抽象工厂类,
抽象工厂类中保存了多个返回值不同的获得实例的方法,每个方法都可以获得一种接口实现的对象实例,
在最终工厂类中会重写抽象工厂类中的某一个或多个获得实例的方法,以此产生不同类型的工厂。
interface Y {
void go();
}
interface Z {
void back();
}
class A implements Y {
@Override
public void go() {
System.out.println("A go!");
}
}
class B implements Y {
@Override
public void go() {
System.out.println("B go!");
}
}
class C implements Z {
@Override
public void back() {
System.out.println("C back!");
}
}
class D implements Z {
@Override
public void back() {
System.out.println("D back!");
}
}
abstract class AbstractFactory {
public abstract Y getInstanceY(String className);
public abstract Z getInstanceZ(String className);
}
public class Factory extends AbstractFactory {
@Override
public Y getInstanceY(String className) {
if (className == null)
return null;
if (className.equals("A"))
return new A();
if (className.equals("B"))
return new B();
return null;
}
@Override
public Z getInstanceZ(String className) {
if (className == null)
return null;
if (className.equals("C"))
return new C();
if (className.equals("D"))
return new D();
return null;
}
}
八、单例模式
保证一个类仅有一个实例,并提供一个访问它的全局访问点。
注意:
1、单例类只能有一个实例。
2、单例类必须自己创建自己的唯一实例。
3、单例类必须给所有其他对象提供这一实例。
- 饿汉式(静态常量,静态常量+静态代码块) (不能延迟加载)
- 懒汉式
1、静态方法中先判断有无实例再创建实例 (线程不安全)
2、同步静态方法 (线程安全但效率太低)
3、在静态方法中只给创建实例过程进行同步(多线程不安全)
-
双检锁/双重校验锁(DCL,即 double-checked locking)
//volatile+双重校验+同步代码块
public class Singleton {
private volatile static Singleton singleton;
private Singleton (){}
public static Singleton getSingleton() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
- 静态内部类
public class Singleton {
private Singleton() {}
private static class SingletonInstance {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return SingletonInstance.INSTANCE;
}
}
//静态内部类方式在Singleton类被装载时并不会立即实例化,而是在需要实例化时,调用getInstance方法,才会装载SingletonInstance类,从而完成Singleton的实例化。
- 枚举(不仅能避免多线程同步问题,而且还自动支持序列化机制,防止反序列化重新创建新的对象,绝对防止多次实例化。但是JDK1.5 之后才加入 enum 特性。)
public enum Singleton {
INSTANCE;
public void whateverMethod() {
}
}
-
容器实现单例模式(利用了Map的key唯一性来保证单例。)
-
CAS实现单例模式
public class Singleton {
private static final AtomicReference<Singleton> INSTANCE = new AtomicReference<Singleton>();
private Singleton() {}
public static Singleton getInstance() {
for (;;) {
Singleton singleton = INSTANCE.get();
if (null != singleton) {
return singleton;
}
singleton = new Singleton();
if (INSTANCE.compareAndSet(null, singleton)) {
return singleton;
}
}
}
}
九、构造者模式
使用多个简单的对象一步一步构建成一个复杂的对象。
一个 Builder 类会一步一步构造最终的对象。该 Builder 类是独立于其他对象的。
参考Java Stream的设计模式
十、原型模式
用于创建重复的对象,同时又能保证性能,提供了一种创建对象的最佳方式。
这种模式是实现了一个原型接口,该接口用于创建当前对象的克隆。当直接创建对象的代价比较大时,则采用这种模式。
(对象克隆)
十一、适配器模式
适配器模式是作为两个不兼容的接口之间的桥梁。这种类型的设计模式属于结构型模式,它结合了两个独立接口的功能。
如何解决:继承或依赖(推荐)。
关键代码:适配器继承或依赖已有的对象,实现想要的目标接口。
使用场景:有动机地修改一个正常运行的系统的接口,这时应该考虑使用适配器模式。
注意事项:适配器不是在详细设计时添加的,而是解决正在服役的项目的问题。
实现:
1、类适配器模式(适配器类继承需要适配的类和新的接口,最终直接new 适配器实例)
2、对象适配器模式(适配器类获得需要适配的类的实例再实现新的接口方法,解决兼容性问题)
3、接口适配器模式(使用抽象适配器类按需实现方法,在用最终适配器类继承抽象适配器类,重写所需方法)
十二、桥接模式
用于把抽象化与实现化解耦,使得二者可以独立变化。
通过提供抽象化和实现化之间的桥接结构,来实现二者的解耦。
主要解决:在有多种可能会变化的情况下,用继承会造成类爆炸问题,扩展起来不灵活。
具体做法:通过定义一个桥接接口,使得实体类的功能独立于接口实现类,降低他们之间的耦合度。
interface Go {
void todo();
}
class Xgo implements Go {
@Override
public void todo() {
System.out.println("X Go!");
}
}
class Ygo implements Go {
@Override
public void todo() {
System.out.println("Y Go!");
}
}
abstract class Person {
private Go go;
public Go getGo() {
return go;
}
public void setGo(Go go) {
this.go = go;
}
}
class Worker extends Person {
public void todo() {
getGo().todo();
}
}
class test {
public static void main(String[] args) {
Worker worker = new Worker();
worker.setGo(new Xgo());
worker.todo();
worker.setGo(new Ygo());
worker.todo();
}
}
十三、过滤器模式
过滤器模式(Filter Pattern)或标准模式(Criteria Pattern)是一种设计模式,这种模式允许开发人员使用不同的标准来过滤一组对象,通过逻辑运算以解耦的方式把它们连接起来。这种类型的设计模式属于结构型模式,它结合多个标准来获得单一标准。
实现:
1、定义具有过滤方法的接口
2、定义过滤器类实现接口中的过滤方法
3、使用过滤器实例中的过滤方法,传入需要过滤的实参,返回过滤结果
十四、组合模式
组合模式,又叫部分整体模式,是用于把一组相似的对象当作一个单一的对象。组合模式依据树形结构来组合对象,用来表示部分以及整体层次。这种类型的设计模式属于结构型模式,它创建了对象组的树形结构。
这种模式创建了一个包含自己对象组的类。该类提供了修改相同对象组的方式。
使用场景:部分、整体场景,如树形菜单,文件、文件夹的管理。
本质:一个森林。
十五、装饰器模式
允许向一个现有的对象添加新的功能,同时又不改变其结构。
这种模式创建了一个装饰类,用来包装原有的类,并在保持类方法签名完整性的前提下,提供了额外的功能。
interface Sourceable {
void go();
}
class Source implements Sourceable {
@Override
public void go() {
System.out.println("Source go!");
}
}
class Decorator implements Sourceable {
private Sourceable source;
public Decorator(Sourceable source) {
this.source = source;
}
@Override
public void go() {
source.go();
System.out.println("Decorator go!");
}
}
class test {
public static void main(String[] args) {
new Decorator(new Source()).go();
}
}
十六、外观模式(门面模式)
向现有的系统添加一个接口,来隐藏系统的复杂性。
本质:
定义一个新的外观类,在外观类中定义多个方法来调用多个类的功能方法,该外观类就是这多个类的门面类。
十七、享元模式
通过对象的复用来减少对象创建的次数和数量,以减少系统内存的使用和降低系统的负载。
在系统需要一个对象时享元模式首先在系统中查找并尝试重用现有的对象,如果未找到匹配的对象,则创建新对象并将其缓存在系统中以便下次使用。(类似于Spring的单例模式)
意图:运用共享技术有效地支持大量细粒度的对象。
主要解决:在有大量对象时,有可能会造成内存溢出,我们把其中共同的部分抽象出来,如果有相同的业务请求,直接返回在内存中已有的对象,避免重新创建。
何时使用: 1、系统中有大量对象。 2、这些对象消耗大量内存。 3、这些对象的状态大部分可以外部化。 4、这些对象可以按照内蕴状态分为很多组,当把外蕴对象从对象中剔除出来时,每一组对象都可以用一个对象来代替。 5、系统不依赖于这些对象身份,这些对象是不可分辨的。
如何解决:用唯一标识码判断,如果在内存中有,则返回这个唯一标识码所标识的对象。
关键代码:用 HashMap 存储这些对象。
应用实例: 1、JAVA 中的 String,如果有则返回,如果没有则创建一个字符串保存在字符串缓存池里面。 2、数据库的数据池。
优点:大大减少对象的创建,降低系统的内存,使效率提高。
缺点:提高了系统的复杂度,需要分离出外部状态和内部状态,而且外部状态具有固有化的性质,不应该随着内部状态的变化而变化,否则会造成系统的混乱。
使用场景: 1、系统有大量相似对象。 2、需要缓冲池的场景。
注意事项: 1、注意划分外部状态和内部状态,否则可能会引起线程安全问题。 2、这些类必须有一个工厂对象加以控制。
十八、代理模式
使用一个类代表另一个类的功能。
两种角色:
- 被代理者
- 代理(Proxy)
应用实例:
1、Windows 里面的快捷方式
2、spring aop
区别:
1、和适配器模式的区别:适配器模式主要改变所考虑对象的接口,而代理模式不能改变所代理类的接口。
2、和装饰器模式的区别:装饰器模式为了增强功能,而代理模式是为了加以控制。
本质:
代理类和被代理类实现相同的接口,在代理类传入被代理类的实例,并通过代理类的实现方法调用被代理类的相同方法,在代理类的实现方法中还可以增加额外的操作。
十九、责任链模式
用于避免请求发送者与多个请求处理者耦合在一起,
让所有请求的处理者持有下一个对象的引用,从而将请求串联成一条链,在有请求发生时,可将所有请求沿着这条链传递,直到遇到该对象的处理器。
应用:jsp servlet 的 Filter,SpringMVC的handler
本质:
1、定义需要Handler接口
2、定义Handler抽象类实现Handler接口
3、定义具体的Handler类继承Handler抽象类,实现所有方法
4、将所有具体的Handler类相互传入形成一个链式结构
5、将所需要处理的业务类实例传入链首的Handler类实例,开始责任链处理。
二十、命令模式
一种数据驱动的设计模式
请求以命令的形式包裹在对象中,并传给调用对象。调用对象寻找可以处理该命令的合适的对象,并把该命令传给相应的对象,该对象执行命令。
优点:
1、降低了系统耦合度。
2、新的命令可以很容易添加到系统中去。
缺点:使用命令模式可能会导致某些系统有过多的具体命令类。
应用: GUI 中每一个按钮都是一条命令。
主要角色:
抽象命令类(ICommand):执行命令的接口,定义执行命令的抽象方法execute()。
具体命令类(Concrete Command):抽象命令类的具体实现类,持有接收者对象,并在接收到命令后调用命令执行者的方法action()实现命令的调用和执行。
命令执行者(Receiver):命令的具体执行者,定义了命令执行和具体方法action()。
命令调用者(Invoker):接收客户端的命令并执行。
二十一、解释器模式
解释器模式(Interpreter Pattern)提供了评估语言的语法或表达式的方式,它属于行为型模式。这种模式实现了一个表达式接口,该接口解释一个特定的上下文。这种模式被用在 SQL 解析、符号处理引擎等。
本质:构建类与类之间的逻辑运算的返回值。
实现:
创建一个接口 Expression 和实现了 Expression 接口的实体类。定义作为上下文中主要解释器的 TerminalExpression 类。其他的类 OrExpression、AndExpression 用于创建组合式表达式。
InterpreterPatternDemo,我们的演示类使用 Expression 类创建规则和演示表达式的解析。
二十二、迭代器模式
用于顺序访问集合对象的元素,不需要知道集合对象的底层表示。
意图:提供一种方法顺序访问一个聚合对象中各个元素, 而又无须暴露该对象的内部表示。
主要解决:不同的方式来遍历整个整合对象。
何时使用:遍历一个聚合对象。
如何解决:把在元素之间游走的责任交给迭代器,而不是聚合对象。
关键代码:定义接口:hasNext, next。
应用实例:JAVA 中的 iterator。
优点: 1、它支持以不同的方式遍历一个聚合对象。 2、迭代器简化了聚合类。 3、在同一个聚合上可以有多个遍历。 4、在迭代器模式中,增加新的聚合类和迭代器类都很方便,无须修改原有代码。
缺点:由于迭代器模式将存储数据和遍历数据的职责分离,增加新的聚合类需要对应增加新的迭代器类,类的个数成对增加,这在一定程度上增加了系统的复杂性。
使用场景: 1、访问一个聚合对象的内容而无须暴露它的内部表示。 2、需要为聚合对象提供多种遍历方式。 3、为遍历不同的聚合结构提供一个统一的接口。
注意事项:迭代器模式就是分离了集合对象的遍历行为,抽象出一个迭代器类来负责,这样既可以做到不暴露集合的内部结构,又可让外部代码透明地访问集合内部的数据。
二十三、中介者模式
意图:用一个中介对象来封装一系列的对象交互,中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。
主要解决:对象与对象之间存在大量的关联关系,这样势必会导致系统的结构变得很复杂,同时若一个对象发生改变,我们也需要跟踪与之相关联的对象,同时做出相应的处理。
应用实例:
- 机场调度系统。
- MVC 框架,其中C(控制器)就是 M(模型)和 V(视图)的中介者。
主要角色:
抽象中介者(Mediator):中介者接口,定义了注册同事对象方法和转发同事对象信息的方法。
具体中介者(Concrete Mediator):中介者接口的实现类,定义了一个List来保存同事对象,协调各个同事角色之间的交互关系。
抽象同事类(Colleague):定义同事类的接口,持有中介者对象,并定义同事对象交互的抽象方法,同时实现同事类的公共方法和功能。
具体同事类(Concrete Colleague):抽象同事类的实现者,在需要与其他同事对象交互时,通过中介者对象来完成。本质:聊天室(多个用户可以向聊天室发送消息,聊天室向所有的用户显示消息)
二十四、备忘录模式(快照模式)
保存一个对象的某个状态,以便在适当的时候恢复对象。
意图:在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。
主要解决:所谓备忘录模式就是在不破坏封装的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样可以在以后将对象恢复到原先保存的状态。
何时使用:很多时候总是需要记录一个对象的内部状态,这样做的目的就是为了允许用户取消不确定或者错误的操作,能够恢复到他原先的状态。
如何解决:通过一个备忘录类专门存储对象状态。
应用实例:
- 打游戏时的存档。
- Windows 里的 ctri + z。
- IE 中的后退。
- 数据库的事务管理。
优点:
- 给用户提供了一种可以恢复状态的机制,可以使用户能够比较方便地回到某个历史的状态。
- 实现了信息的封装,使得用户不需要关心状态的保存细节。
缺点:消耗资源。如果类的成员变量过多,势必会占用比较大的资源,而且每一次保存都会消耗一定的内存。
使用场景:
- 需要保存/恢复数据的相关状态场景。
- 提供一个可回滚的操作。
注意事项:
- 为了符合迪米特原则,还要增加一个管理备忘录的类。
- 为了节约内存,可使用原型模式+备忘录模式。
主要角色:
发起人(Originator):记录当前时刻对象的内部状态,定义创建备忘录和恢复备忘录数据的方法。
备忘录(Memento):负责存储对象的内部状态。
状态管理者(Storage):对备忘录的历史状态进行存储,定义了保存和获取备忘录状态的功能。注意,备忘录只能被保存或恢复,不能进行修改。
二十五、观察者模式
当对象间存在一对多关系时,则使用观察者模式(Observer Pattern)。比如,当一个对象被修改时,则会自动通知依赖它的对象。
意图:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
主要解决:一个对象状态改变给其他对象通知的问题,而且要考虑到易用和低耦合,保证高度的协作。
何时使用:一个对象(目标对象)的状态发生改变,所有的依赖对象(观察者对象)都将得到通知,进行广播通知。
关键代码:在抽象类里有一个 ArrayList存放观察者们。
优点:
- 观察者和被观察者是抽象耦合的。
- 建立一套触发机制。
缺点:
- 如果一个被观察者对象有很多的直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。
- 如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃。
- 观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化。
使用场景:
- 一个抽象模型有两个方面,其中一个方面依赖于另一个方面。将这些方面封装在独立的对象中使它们可以各自独立地改变和复用。
- 一个对象的改变将导致其他一个或多个对象也发生改变,而不知道具体有多少对象将发生改变,可以降低对象之间的耦合度。
- 一个对象必须通知其他对象,而并不知道这些对象是谁。
- 需要在系统中创建一个触发链,A对象的行为将影响B对象,B对象的行为将影响C对象……,可以使用观察者模式创建一种链式触发机制。
注意事项:
- JAVA 中已经有了对观察者模式的支持类。(java9新增了一个发布-订阅框架,比较轻量级)
- 避免循环引用。
- 如果顺序执行,某一观察者错误会导致系统卡壳,一般采用异步方式。
主要角色:
抽象主题(Subject):持有订阅了该主题的观察者对象的集合,同时提供了增加、删除观察者对象的方法和主题状态发生变化后的通知方法。
具体主题(Concrete Subject):实现了抽象主题的通知方法,在主题的内部状态发生变化时,调用该方法通知订阅了主题状态的观察者对象。
抽象观察者(Observer):观察者的抽象类或接口,定义了主题状态发生变化时需要调用的方法。
具体观察者(Concrete Observer):抽象观察者的实现类,在收到主题状态变化的信息后执行具体的触发机制。附:Java 9新增的发布-订阅框架
Java 9新增了一个发布-订阅框架,该框架是基于异步响应流的。这个发布-订阅框架可以非常方便地处理异步线程之间的流数据交换(比如两个线程之间需要交换数据)。而且这个发布-订阅框架不需要使用数据中心来缓冲数据,同时具有非常高效的性能。
这个发布-订阅框架使用Flow类的4个静态内部接口作为核心API。
➢ Flow.Publisher:代表数据发布者、生产者。
➢ Flow.Subscriber:代表数据订阅者、消费者。
➢ Flow.Subscription:代表发布者和订阅者之间的链接纽带。订阅者既可通过调用该对象的request()方法来获取数据项,也可通过调用对象的cancel()方法来取消订阅。➢ Flow.Processor:数据处理器,它可同时作为发布者和订阅者使用。
Flow.Publisher发布者作为生产者,负责发布数据项,并注册订阅者。Flow.Publisher接口定义了如下方法来注册订阅者。
➢ void subscribe(Flow.Subscriber<?super T>subscriber):程序调用此方法注册订阅者时,会触发订阅者的onSubscribe()方法,而Flow.Subscription对象作为参数传给该方法;如果注册失败,将会触发订阅者的onError() 方法。
Flow.Subscriber接口定义了如下方法。
➢ void onSubscribe(Flow.Subscription subscription):订阅者注册时自动触发该方法。
➢ void onComplete():当订阅结束时触发该方法。
➢ void onError(Throwable throwable):当订阅失败时触发该方法。
➢ void onNext(T item):订阅者从发布者处获取数据项时触发该方法,订阅者可通过该方法获取数据项。
为了处理一些通用发布者的场景,Java 9为Flow.Publisher提供了一个SubmissionPublisher实现类,它可向当前订阅者异步提交非空的数据项,直到它被关闭。每个订阅者都能以相同的顺序接收到新提交的数据项。
程序创建SubmissionPublisher对象时,需要传入一个线程池作为底层支撑;该类也提供了一个无参数的构造器,该构造器使用ForkJoinPool.commonPool()方法来提交发布者,以此实现发布者向订阅者提供数据项的异步特性。
二十六、状态模式
在状态模式中,类的行为是基于它的状态改变的。
在状态模式中,需要创建表示各种状态的对象和一个行为随着状态对象改变而改变的 context 对象。
意图:允许对象在内部状态发生改变时改变它的行为,对象看起来好像修改了它的类。
主要解决:对象的行为依赖于它的状态(属性),并且可以根据它的状态改变而改变它的相关行为。
使用场景: 1、行为随状态改变而改变的场景。 2、条件、分支语句的代替者。
注意事项:在行为受状态约束的时候使用状态模式,而且状态不超过 5 个。
主要角色:
环境(Context):也叫作上下文,用于维护对象当前的状态,并在对象状态发生变化时触发对象行为的变化。
抽象状态(AbstractState):定义了一个接口,用于定义对象中不同状态所对应的行为。
具体状态(Concrete State):实现抽象状态所定义的行为。本质:State实例在执行完动作方法时,都会更新Context中的状态值,以此来做到状态驱动
二十七、空对象模式
使用一个空对象取代 NULL 对象实例的检查。Null 对象不是检查空值,而是反应一个不做任何动作的关系。这样的 Null 对象也可以在数据不可用的时候提供默认的行为。
在空对象模式中,我们创建一个指定各种要执行的操作的抽象类和扩展该类的实体类,还创建一个未对该类做任何实现的空对象类,该空对象类将无缝地使用在需要检查空值的地方。
本质:避免空指针异常
二十八、策略模式
让一个类的行为或其算法可以在运行时更改。
在策略模式中,创建表示各种策略的对象和一个行为随着策略对象改变而改变的 context 对象。策略对象改变 context 对象的执行算法。
意图:定义一系列的算法,把它们一个个封装起来, 并且使它们可相互替换。
主要解决:在有多种算法相似的情况下,使用 if...else 所带来的复杂和难以维护。
何时使用:一个系统有许多许多类,而区分它们的只是他们直接的行为。
如何解决:将这些算法封装成一个一个的类,任意地替换。
关键代码:实现同一个接口。
应用实例: JAVA AWT 中的 LayoutManager。
优点:
- 算法可以自由切换。
- 避免使用多重条件判断。
- 扩展性良好。
缺点:
- 策略类会增多。
- 所有策略类都需要对外暴露。
使用场景:
- 如果在一个系统里面有许多类,它们之间的区别仅在于它们的行为,那么使用策略模式可以动态地让一个对象在许多行为中选择一种行为。
- 一个系统需要动态地在几种算法中选择一种。
- 如果一个对象有很多的行为,如果不用恰当的模式,这些行为就只好使用多重的条件选择语句来实现。
注意事项:如果一个系统的策略多于四个,就需要考虑使用混合模式(组合模式),解决策略类膨胀的问题。
本质:
- 策略模式的代价是减少if...else...的同时也面临着需要开发者需要根据不同的场景去选用不同的策略,性能的提升是有所提升,但也会导致程序变得更复杂(策略过多就会导致策略膨胀),我个人并不喜欢策略模式,被AWT的LayoutManager搞怕了,瑟瑟发抖~~~
- 策略类中通过方法传入的不同的类对象来实现多种不同的功能,其本质还是将if...else...的判断选择权交给了用户去做抉择,我觉得是有违初心的,使用程序无非是希望程序帮助我们进行判断,如果一开始我们就知道使用哪种策略,也就没必要去使用一个模式去完成,直接使用该策略即可。
- 但存在即合理(策略模式的存在增强了代码的维护性),在这里我力荐一种内容协商策略,原理也很简单,但实用性非常高,有兴趣的读者可以自行查阅相关文献。
二十九、模板模式
一个抽象类公开定义了执行它的方法的方式/模板。它的子类可以按需要重写方法实现,但调用将以抽象类中定义的方式进行。
应用实例:太多了。。。(jdk源码里面随便翻一翻都是)
注意事项:为防止恶意操作,一般模板方法都加上 final 关键词。
本质:没啥好说的,就是抽象类,这总不能不会吧。。。
三十、访问者模式
使用一个访问者类来改变了元素类的执行算法。
通过这种方式,元素的执行算法可以随着访问者改变而改变。
根据模式,元素对象已接受访问者对象,这样访问者对象就可以处理元素对象上的操作。
意图:主要将数据结构与数据操作分离。
注意事项:访问者可以对功能进行统一,可以做报表、UI、拦截器与过滤器。
本质:
1、具体访问者重写访问者抽象类(该类中同过方法重载定义了所有使用者类的方法参数传入),实现不同的功能方法
2、使用者调用accept()方法时传入创建好访问者实例,accept()方法内部会调用访问者实例方法(该方法因为是一个重载方法,所以会根据传入的不同参数实现不同的功能)来实现功能。
至此,常见的23种设计模式全部介绍完毕!(其中空对象模式除外)
三十一、MVC模式
MVC 模式代表 Model-View-Controller(模型-视图-控制器) 模式。这种模式用于应用程序的分层开发。
- Model(模型) - 模型代表一个存取数据的对象或 JAVA POJO。它也可以带有逻辑,在数据变化时更新控制器。
- View(视图) - 视图代表模型包含的数据的可视化。
- Controller(控制器) - 控制器作用于模型和视图上。它控制数据流向模型对象,并在数据变化时更新视图。它使视图与模型分离开。
一般来说(SpringMVC中):
- M:Service、Dao、POJO
- V:View
- C:Controller
三十二、业务代表模式
业务代表模式(Business Delegate Pattern)用于对表示层和业务层解耦。它基本上是用来减少通信或对表示层代码中的业务层代码的远程查询功能。在业务层中我们有以下实体。
- 客户端(Client) - 表示层代码可以是 JSP、servlet 或 UI java 代码。
- 业务代表(Business Delegate) - 一个为客户端实体提供的入口类,它提供了对业务服务方法的访问。
- 查询服务(LookUp Service) - 查找服务对象负责获取相关的业务实现,并提供业务对象对业务代表对象的访问。
- 业务服务(Business Service) - 业务服务接口。实现了该业务服务的实体类,提供了实际的业务实现逻辑。
本质:看不懂吧!就是Service与Service之间解耦,Client通过Delegate来实现对不同Service对象的获取(类似于工厂模式)
三十三、组合实体模式
组合实体模式(Composite Entity Pattern)用在 EJB 持久化机制中。一个组合实体是一个 EJB 实体 bean,代表了对象的图解。当更新一个组合实体时,内部依赖对象 beans 会自动更新,因为它们是由 EJB 实体 bean 管理的。以下是组合实体 bean 的参与者。
- 组合实体(Composite Entity) - 它是主要的实体 bean。它可以是粗粒的,或者可以包含一个粗粒度对象,用于持续生命周期。
- 粗粒度对象(Coarse-Grained Object) - 该对象包含依赖对象。它有自己的生命周期,也能管理依赖对象的生命周期。
- 依赖对象(Dependent Object) - 依赖对象是一个持续生命周期依赖于粗粒度对象的对象。
- 策略(Strategies) - 策略表示如何实现组合实体。
本质:直接来看本质吧!就是批量操作多个不同类型对象的动作(赋值,传参,方法调用等等),原因在于Java是强类型语言,无法做到类型之间隐式转换,比如如果要一个方法要返回多个类型不同的对象集合,要么使用泛型将这些对象类型泛化为Object,再打包成数组或者集合返回,要么把多个类型不同的对象封装在一个类中,由该类提供相关的对外动作,显然组合设计模式是第二者。
三十四、数据访问对象模式
数据访问对象模式(Data Access Object Pattern)或 DAO 模式用于把低级的数据访问 API 或操作从高级的业务服务中分离出来。以下是数据访问对象模式的参与者。
- 数据访问对象接口(Data Access Object Interface) - 该接口定义了在一个模型对象上要执行的标准操作。
- 数据访问对象实体类(Data Access Object concrete class) - 该类实现了上述的接口。该类负责从数据源获取数据,数据源可以是数据库,也可以是 xml,或者是其他的存储机制。
- 模型对象/数值对象(Model Object/Value Object) - 该对象是简单的 POJO,包含了 get/set 方法来存储通过使用 DAO 类检索到的数据。
本质:就是Dao和POJO之间的关系。
三十五、前端控制器模式
前端控制器模式(Front Controller Pattern)是用来提供一个集中的请求处理机制,所有的请求都将由一个单一的处理程序处理。该处理程序可以做认证/授权/记录日志,或者跟踪请求,然后把请求传给相应的处理程序。以下是这种设计模式的实体。
- 前端控制器(Front Controller) - 处理应用程序所有类型请求的单个处理程序,应用程序可以是基于 web 的应用程序,也可以是基于桌面的应用程序。
- 调度器(Dispatcher) - 前端控制器可能使用一个调度器对象来调度请求到相应的具体处理程序。
- 视图(View) - 视图是为请求而创建的对象。
参考:SpringMVC的DispatcherServlet
三十六、拦截过滤器模式
拦截过滤器模式(Intercepting Filter Pattern)用于对应用程序的请求或响应做一些预处理/后处理。定义过滤器,并在把请求传给实际目标应用程序之前应用在请求上。过滤器可以做认证/授权/记录日志,或者跟踪请求,然后把请求传给相应的处理程序。以下是这种设计模式的实体。
- 过滤器(Filter) - 过滤器在请求处理程序执行请求之前或之后,执行某些任务。
- 过滤器链(Filter Chain) - 过滤器链带有多个过滤器,并在 Target 上按照定义的顺序执行这些过滤器。
- Target - Target 对象是请求处理程序。
- 过滤管理器(Filter Manager) - 过滤管理器管理过滤器和过滤器链。
- 客户端(Client) - Client 是向 Target 对象发送请求的对象。
要弄明白:拦截、过滤是不一样的;
举例:在SpringMVC中搞了一个Interceptor(拦截器),是基于函数回调的,更像拦截过滤器模式,但是JavaWeb中的Filter是原生支持的(本质也是拦截过滤器模式),而且二者作用的位置也不相同。
三十七、服务定位器模式
服务定位器模式(Service Locator Pattern)用在我们想使用 JNDI 查询定位各种服务的时候。考虑到为某个服务查找 JNDI 的代价很高,服务定位器模式充分利用了缓存技术。在首次请求某个服务时,服务定位器在 JNDI 中查找服务,并缓存该服务对象。当再次请求相同的服务时,服务定位器会在它的缓存中查找,这样可以在很大程度上提高应用程序的性能。以下是这种设计模式的实体。
- 服务(Service) - 实际处理请求的服务。对这种服务的引用可以在 JNDI 服务器中查找到。
- Context / 初始的 Context - JNDI Context 带有对要查找的服务的引用。
- 服务定位器(Service Locator) - 服务定位器是通过 JNDI 查找和缓存服务来获取服务的单点接触。
- 缓存(Cache) - 缓存存储服务的引用,以便复用它们。
- 客户端(Client) - Client 是通过 ServiceLocator 调用服务的对象。
三十八、传输对象模式
传输对象模式(Transfer Object Pattern)用于从客户端向服务器一次性传递带有多个属性的数据。传输对象也被称为数值对象。传输对象是一个具有 getter/setter 方法的简单的 POJO 类,它是可序列化的,所以它可以通过网络传输。它没有任何的行为。服务器端的业务类通常从数据库读取数据,然后填充 POJO,并把它发送到客户端或按值传递它。对于客户端,传输对象是只读的。客户端可以创建自己的传输对象,并把它传递给服务器,以便一次性更新数据库中的数值。以下是这种设计模式的实体。
- 业务对象(Business Object) - 为传输对象填充数据的业务服务。
- 传输对象(Transfer Object) - 简单的 POJO,只有设置/获取属性的方法。
- 客户端(Client) - 客户端可以发送请求或者发送传输对象到业务对象。
本质:
1、使用了对象序列化和反序列化
2、从此RPC、微服务、分布式等等,玩的花里胡哨。