
本文共 27200 字,大约阅读时间需要 90 分钟。
23种设计模式-总结
如想了解更多更全面的Java必备内容可以阅读:所有JAVA必备知识点面试题文章目录:
文章目录
* 你知道设计模式都分别有哪些?
总体来说设计模式分为三大类:
- 创建型模式(都是用来帮助我们创建对象的,共有五种):单例模式、工厂方法模式、抽象工厂模式、建造者模式、原型模式。
- 结构型模式(关注对象和类的组织。共有七种):代理模式、适配器模式、桥接模式、组合模式、装饰模式、外观模式、享元模式。
- 行为型模式(关注系统中对象之间的相互交换,研究系统在运行时对象之间的相互通信和协作,进一步明确对象的职责。共有十一种):责任链模式、命令模式、解释器模式、迭代器模式、中介者模式、备忘录模式、观察者模式、状态模式、策略模式、模板方法、访问者模式。
* 设计模式的六原则,一法则?
- 开闭原则 核心:一个软件实体应当对扩展开放,对修改关闭。即软件实体应尽量在不修改原有代码的情况下进行扩展。【告诉我们要对扩展开放,对修改关闭。】
- 里氏代换原则 核心:所有引用基类(父类)的地方,都必须能透明地使用其子类的对象。【子类可以扩展父类的功能,但不能改变父类原有的功能】
- 依赖倒转原则 核心:抽象不应该依赖于细节,细节应当依赖于抽象。换言之,要针对接口编程,而非针对实现编程。【告诉我们要面向接口编程】
- 单一职责原则 核心:一个类只负责一个功能领域中相应的职责,或者可以定义为:就一个类而言,应该只有一个引起它变化的原因。 【告诉我们实现类要职责单一】
- 接口隔离原则 核心:使用多个专门的接口,而不使用单一的总接口。即 客户端不应该依赖于那些它不需要的接口。【告诉我们在设计接口的时候要精简单一】
- 合成复用原则 核心:要求在软件复用时,要尽量先使用组合或者聚合等关联关系来实现,其次才考虑使用继承关系来实现。如果要使用继承关系,则必须严格遵循里氏替换原则。
- 迪米特法则 核心:又叫最少知道原则,一个类对其所依赖的类知道得越少越好。及一个软件实体应当尽可能少地与其他实体发生作用。【告诉我们要降低耦合】
* 列举Spring中常见的设计模式都有哪些?
设计模式名称 | 举例 |
---|---|
工厂模式 | BeanFactory |
装饰器模式 | BeanWrapper |
代理模式 | AopProxy |
委派模式 | DispatcherServlet |
策略模式 | HandlerMapping |
适配器模式 | HandlerAdapter |
模板模式 | JdbcTemplate |
观察者模式 | ContextLoaderListener |
一、创建型
1、单例模式
定义: 是保证一个类只有一个实例,并且提供一个访问实例的全局访问点。
特点: 1、只有一个实例。 2、自我实例化。 3、提供全局访问点。 单例的实现方式: 饿汉式、懒汉式、双重检测锁式 、静态内部类式、枚举单例等。饿汉式: 是在类加载的时候就立即初始化,并且创建单例对象。线程安全。
- 优点:没有加任何的锁、执行效率比较高,在用户体验上来说,比懒汉式更好。
- 缺点:类加载的时候就初始化,不管用与不用都占着空间,浪费了内存。
/** * 单例模式:饿汉式,线程安全 * @author 精彩猿笔记 */public class SingletonInstance { // 声明此类型的变量,并实例化,当该类被加载的时候就完成了实例化并保存在了内存中 private static final SingletonInstance instance = new SingletonInstance(); // 私有化所有的构造方法,防止直接通过new关键字实例化 private SingletonInstance(){ //防止通过反射的方式生成多个实例 if(instance != null){ // 只能有一个实例存在,如果再次调用该构造方法就抛出异常,防止反射方式实例化 throw new RuntimeException("单例模式只能创建一个对象"); } } // 对外提供一个获取实例的静态方法 public static SingletonInstance getInstance(){ return instance; }}
懒汉式: 被外部类调用的时候内部类才会加载。线程不安全。
/** * 单例模式:懒汉式,线程不安全 * @author 精彩猿笔记 */public class SingletonInstance { // 声明此类型的变量,但没有实例化 private static SingletonInstance instance = null; // 私有化所有的构造方法,防止直接通过new关键字实例化 private SingletonInstance(){ //防止通过反射的方式生成多个实例 if(instance != null){ // 只能有一个实例存在,如果再次调用该构造方法就抛出异常,防止反射方式实例化 throw new RuntimeException("单例模式只能创建一个对象"); } } // 对外提供一个获取实例的静态方法,为了数据安全添加synchronized关键字 public static SingletonInstance getInstance(){ // 当instance不为空的时候才实例化 instance = new SingletonInstance(); return instance; }}
双重检测锁式:懒汉式的线程安全版本。
/** * 单例模式:懒汉式【双重检测机制】 线程安全 * @author 精彩猿笔记 */public class SingletonInstance { // 声明此类型的变量,但没有实例化,volatile-禁止重排序 private volatile static SingletonInstance instance = null; // 私有化所有的构造方法,防止直接通过new关键字实例化 private SingletonInstance(){ //防止通过反射的方式生成多个实例 if(instance != null){ // 只能有一个实例存在,如果再次调用该构造方法就抛出异常,防止反射方式实例化 throw new RuntimeException("单例模式只能创建一个对象"); } } // 对外提供一个获取实例的静态方法, public static SingletonInstance getInstance(){ if(instance == null){ synchronized(SingletonInstance.class){ if(instance == null){ instance = new SingletonInstance(); } } } return instance; }}
静态内部类式:
/** * 单例模式:静态内部类实现方式 线程安全 * @author 精彩猿笔记 */public class SingletonInstance { // 静态内部类 public static class SingletonClassInstance{ // 声明外部类型的静态常量 public static final SingletonInstance instance = new SingletonInstance(); } // 私有化构造方法 private SingletonInstance(){ //防止通过反射的方式生成多个实例 if(instance != null){ // 只能有一个实例存在,如果再次调用该构造方法就抛出异常,防止反射方式实例化 throw new RuntimeException("单例模式只能创建一个对象"); } } // 对外提供的唯一获取实例的方法 public static SingletonInstance getInstance(){ return SingletonClassInstance.instance; }}
/** * 单例模式:枚举方式实现,线程安全 * 【官方推荐的一种方式,可以防止序列化和反射的破坏】 * @author 精彩猿笔记 */public enum SingletonInstance { // 定义一个枚举元素,则这个元素就代表了SingletonInstance5的实例 INSTANCE; public static EnumSingleton getInstance(){ return INSTANCE; }}public static void main(String[] args) { SingletonInstance s1 = SingletonInstance.getInstance(); SingletonInstance s2 = SingletonInstance.getInstance(); System.out.println(s1 == s2); // 输出的是 true}#### 说明一下:字节码底层实现如下:static{ INSTANCE = new SingletonInstance ("INSTANCE", 0); $VALUES = (new SingletonInstance [] { INSTANCE });}
单例模式漏洞:
- 通过反射的方式我们依然可用获取多个实例(除了枚举的方式);【解决的方式是在无参构造方法中手动抛出异常控制】
// 私有化所有的构造方法,防止直接通过new关键字实例化private SingletonInstance2(){ if(instance != null){ // 只能有一个实例存在,如果再次调用该构造方法就抛出异常,防止反射方式实例化 throw new RuntimeException("单例模式只能创建一个对象"); }}
- 通过反序列化的方式也可以破解上面几种方式(除了枚举的方式)【我们只需要在单例类中重写readResolve() 方法并在该方法中返回单例对象即可】
// 声明此类型的变量,但没有实例化 private static SingletonInstance instance = null; // 私有化所有的构造方法,防止直接通过new关键字实例化 private SingletonInstance(){ if(instance != null){ // 只能有一个实例存在,如果再次调用该构造方法就抛出异常,防止反射方式实例化 throw new RuntimeException("单例模式只能创建一个对象"); } } // 对外提供一个获取实例的静态方法,为了数据安全添加synchronized关键字 public static synchronized SingletonInstance getInstance(){ if(instance == null){ // 当instance不为空的时候才实例化 instance = new SingletonInstance(); } return instance; } // 重写该方法,防止序列化和反序列化获取实例 private Object readResolve() throws ObjectStreamException{ return instance; }
大家一定会关心这是什么原因呢?为什么要这样写? 看看 DK的源码实现就一清二楚了 。 我们进入ObjectInputStream 类的 readObject()方法,代码如下:public final Object readObject() throws IOException, ClassNotFoundException{ if (enableOverride) { return readObjectOverride(); } // if nested read, passHandle contains handle of enclosing object int outerHandle = passHandle; try { //我们发现在readObject中又调用了我们重写的readObject0()方法。 Object obj = readObject0(false); handles.markDependency(outerHandle, passHandle); ClassNotFoundException ex = handles.lookupException(passHandle); if (ex != null) { throw ex; } if (depth == 0) { vlist.doCallbacks(); } return obj; } finally { passHandle = outerHandle; if (closed && depth == 0) { clear(); } }}####### 进入readObject0()方法,,代码如下:private Object readObject0(boolean unshared) throws IOException { ....... try { switch (tc) { ....... //为什么枚举方式不会被反射和序列化破坏,jdk底层对其有特殊处理逻辑 case TC_ENUM: return checkResolve(readEnum(unshared)); case TC_OBJECT: //看到TC_OBJECTD中判断,调用了 ObjectInputStream 的 readOrdinaryObject()方法 return checkResolve(readOrdinaryObject(unshared)); ....... }####### 我们继续进入看readOrdinaryObject()方法源码:private Object readOrdinaryObject(boolean unshared) throws IOException{ ....... //判断是否重写ReadResolve方法,如果重写这个方法,则听过反射调用重写的方法。 if (obj != null && handles.lookupException(passHandle) == null && desc.hasReadResolveMethod()) { Object rep = desc.invokeReadResolve(obj); if (unshared && rep.getClass().isArray()) { rep = cloneArray(rep); } if (rep != obj) { handles.setObject(passHandle, obj = rep); } } return obj;}
单例的使用场景:
- Spring中bean对象的模式实现方式。
- servlet中每个servlet的实例。
- spring mvc和struts1框架中,控制器对象是单例模式。
- 应用程序的日志应用,一般都何用单例模式实现,这一般是由于共享的日志文件一直处于打开状态,因为只能有一个实例去操作,否则内容不好追加。
- 操作系统的文件系统,也是大的单例模式实现的具体例子,一个操作系统只能有一个文件系统。
- 项目中,读取配置文件的类,一般也只有一个对象。没有必要每次使用配置文件数据,每次new一个对象去读取。
- 数据库连接池的设计一般也是采用单例模式,因为数据库连接是一种数据库资源。
- …… 等等
2、简单工厂模式 (它不属于23 种设计模式)
三种工厂模式的区别:
模式 | 说明 |
---|---|
简单工厂 | 用来生产同一等级结构中的任意产品。(对于增加新的产品,需要修改已有代码) |
工厂方法 | 用来生产同一等级结构中的固定产品。(支持增加任意产品) |
抽象工厂 | 用来生产不同产品族的全部产品。(对于增加新的产品,无能为力;支持增加产品族) |
核心本质:
实例化对象,用工厂方法代替new操作。 将选择实现类、创建对象统一管理和控制。从而将调用者跟我们的实现类解耦。 简单工厂模式 也叫静态工厂模式,就是工厂类一般是使用静态方法,通过接收的参数的不同来返回不同的对象实例。对于增加新产品无能为力!不修改代码的话,是无法扩展的。 违背开闭原则和单一原则。 缺点:工厂类的职责相对过重,不易于扩展过于复杂的产品结构。
3、工厂方法模式
工厂方法模式 指定义一个创建对象的接口,但让实现这个接口的类来决定实例化哪个类,工厂方法让类的实例化推迟到子类中进行。
工厂方法模式非常符合“开闭原则”。
当需要增加一个新的产品时,我们只需要增加一个具体的产品类和与之对应的具体工厂即可,无须修改原有系统。同时在工厂方法模式中用户只需要知道生产产品的具体工厂即可,无须关系产品的创建过程,甚至连具体的产品类名称都不需要知道。适用场景:
1、创建对象需要大量重复的代码。 2、客户端(应用层)不依赖于产品类实例如何被创建、实现等细节。 3、一个类通过其子类来指定创建哪个对象。缺点:
1、类的个数容易过多,增加复杂度。 2、增加了系统的抽象性和理解难度。UML结构图:

4、抽象工厂模式
抽象工厂模式 就是提供一个接口,用于创建相关或者依赖对象的家族,而不需要明确指定具体类。它允许客户端使用抽象的接口来创建一组相关的产品,而不需要关系实际产出的具体产品是什么。这样一来,客户就可以从具体的产品中被解耦。
优点:隔离了具体类的生成,使得客户端不需要知道什么被创建了。
缺点:新增新的行为会比较麻烦,因为当添加一个新的产品对象时,需要更加需要更改接口及其下所有子类。
其UML结构图:

5、建造者模式
在现实生活中如果我们需要制造一个比较复杂的东西,比如手机,台式电脑,或者汽车等。如果我们要制造一台电脑的话我们会先将电脑所需的各个部件买回来然后在组装起来成为一台电脑。这里电脑所需的各个组件比如显示器,CPU,硬盘等等都是由不同的厂商生产的,然后被我们不同的组合而成了不同的产品。
建造者模式 将复杂产品的构建过程封装分解在不同的方法中,使得创建过程非常清晰,能够让我们更加精确的控制复杂产品对象的创建过程,同时它隔离了复杂产品对象的创建和使用,使得相同的创建过程能够创建不同的产品。UML结构图:

- 分离了对象子组件的单独构造(由Builder来负责)和装配(由Director负责)。 从而可以构造出复杂的对象。这个模式适用于:某个对象的构建过程复杂的情况下使用。
- 由于实现了构建和装配的解耦。不同的构建器,相同的装配,也可以做出不同的对象;相同的构建器,不同的装配顺序也可以做出不同的对象。也就是实现了构建算法、装配算法的解耦,实现了更好的复用
开发中应用场景:
- StringBuilder类的append方法
- SQL中的PreparedStatement
- JDOM中,DomBuilder、SAXBuilder
- ……等等
6、原型模式
原型模式 也称为克隆模式,即一个某个对象为原型克隆出来一个一模一样的对象,该对象的属性和原型对象一模一样。而且对于原型对象没有任何影响。在我们应用程序可能有某些对象的结构比较复杂,但是我们又需要频繁的使用它们,如果这个时候我们来不断的新建这个对象势必会大大损耗系统内存的。
常见的适用场景:
- 类初始化消耗资源较多。
- new 产生的一个对象需要非常繁琐的过程(数据准备、访问权限等)
- 构造函数比较复杂。
- 循环体中生产大量对象时。
原型模式的克隆方式有两种:浅克隆 和 深度克隆
原型模式 | 说明 |
---|---|
浅克隆 | 只是拷贝本对象,其对象内部的数组、引用对象等都不拷贝,还是指向原生对象的内部元素地址 |
深度克隆 | 深复制把要复制的对象所引用的对象都复制了一遍 |
浅克隆: 被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象。换言之,浅复制仅仅复制所考虑的对象,而不复制它所引用的对象。 Object类提供的方法clone只是拷贝本对象 , 其对象内部的数组、引用对象等都不拷贝,还是指向原生对象的内部元素地址。
浅克隆-实现方式: 被克隆的对象必须Cloneable,Serializable这两个接口public class User2 implements Cloneable,Serializable{ private String name; private Date birth; ..... /** * 实现克隆的方法 */ public Object clone() throws CloneNotSupportedException{ return super.clone(); }}
深度克隆: 被复制对象的所有变量都含有与原来的对象相同的值,除去那些引用其他对象的变量。那些引用其他对象的变量将指向被复制过的新对象,而不再是原有的那些被引用的对象。换言之,深复制把要复制的对象所引用的对象都复制了一遍。
深度克隆-实现方式: 1)常用的ArrayList就实现了Cloneable接口,属于深克隆,来看代码clone()方法的实现:【可以理解为:另开辟一块相同的内存空间,依次将原来对象复制一份到新的内存空间中】public Object clone() { try { ArrayList v = (ArrayList ) super.clone(); v.elementData = Arrays.copyOf(elementData, size); v.modCount = 0; return v; } catch (CloneNotSupportedException e) { // this shouldn't happen, since we are Cloneable throw new InternalError(e); } } public staticT[] copyOf(U[] original, int newLength, Class newType) { @SuppressWarnings("unchecked") T[] copy = ((Object)newType == (Object)Object[].class) ? (T[]) new Object[newLength] : (T[]) Array.newInstance(newType.getComponentType(), newLength); System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength)); return copy; }
2)序列化和反序列化:
名称 | 说明 |
---|---|
序列化 | 把对象转换为字节序列的过程。 |
反序列化 | 把字节序列恢复为对象的过程。 |
二、结构型
7、代理模式
代理模式 就是给一个对象提供一个代理,并由代理对象控制对原对象的引用。它使得客户不能直接与真正的目标对象通信。
静态代理模式: 若代理类在程序运行前就已经存在,那么这种代理方式被成为 静态代理 ,这种情况下的代理类通常都是我们在Java代码中定义的。 通常情况下, 静态代理中的代理类和目标类会实现同一接口或是派生自相同的父类。
动态代理模式: 代理类在程序运行时创建的代理方式被成为 动态代理。 也就是说,这种情况下,代理类并不是在Java代码中定义的,而是在运行时根据我们在Java代码中的“指示”动态生成的。代理类型 | 使用场景 |
---|---|
JDK动态代理 | 如果目标对象实现了接口,默认采用JDK的动态代理,可以强制使用CGLIB实现AOP |
CGLIB动态代理 | 如果目标对象没有实现了接口,必须采用CGLIB动态代理,spring会自动在JDK动态代理和CGLIB之间转换。 |
CGLIB与JDK动态代理区别:
- JDK动态代理是实现了被代理对象的接口,CGLib 是继承了被代理对象。
- JDK和CGLib都是在运行期生成字节码,JDK是直接写Class字节码,CGLib使用ASM框架写Class字节码,Cglib代理实现更复杂,生成代理类比JDK 效率低。
- JDK调用代理方法,是通过反射机制调用,CGLib是通过FastClass机制直接调用方法,CGLib执行效率更高。
JDK 动态代理生成对象的步骤,如下:
- 拿到被代理对象的引用,并且通过反射获取到它的所有的接口。
- JDK代理类重新生成一个新的类、同时新的类要实现被代理类所有实现的所有的接口。
- 动态生成Java代码,把新加的业务逻辑方法由一定的逻辑代码去调用(在代码中体现)。
- 编译新生成的Java代码.class。
- 再重新加载到 JVM 中运行。
静态代理和动态代理的本质区别:
- 静态代理只能通过手动完成代理操作,如果被代理类增加新的方法,代理类需要同步新增,违背开闭原则。
- 动态代理采用在运行时动态生成代码的方式,取消了对被代理类的扩展限制,遵循开闭原则。
- 若动态代理要对目标类的增强逻辑扩展,结合策略模式,只需要新增策略类便可完成,无需修改代理类的代码。
Spring 中的代理选择原则:
- 当 Bean有实现接口时,Spring就会用JDK的动态代理
- 当 Bean没有实现接口时,Spring选择 CGLib。
- Spring 可以通过配置强制使用CGLib,只需在Spring的配置文件中加入如下代码:
代理模式包含如下角色:
- Subject: 抽象主题
- Proxy: 代理主题
- RealSubject: 真实主题
UML结构图:

8、适配器模式
在我们的应用程序中我们可能需要将两个不同接口的类来进行通信,在不修改这两个的前提下我们可能会需要某个中间件来完成这个衔接的过程。这个中间件就是适配器。
适配器模式 就是将一个类的接口,转换成客户期望的另一个接口。它可以让原本两个不兼容的接口能够无缝完成对接。适配器模式包含如下角色:
- 目标接口(Target):客户所期待的接口。目标可以是具体的或抽象的类,也可以是接口。
- 需要适配的类(Adaptee):需要适配的类或适配者类。
- 适配器(Adapter):通过包装一个需要适配的对象,把原接口转换成目标接口
- 客户类(Client)
UML结构图:

----------------------------------------------------------------/** * 被适配的类 * @author 精彩猿笔记 */public class Adaptee { public void request(){ System.out.println("可以完成客户请求的需要的功能...."); }}----------------------------------------------------------------/** * 目标接口 * @author 精彩猿笔记 */public interface Target { /**处理请求的方法*/ void handleReq();}----------------------------------------------------------------/** * 适配器 * @author 精彩猿笔记 */public class Adapter extends Adaptee implements Target { @Override public void handleReq() { super.request(); }}----------------------------------------------------------------public static void main(String[] args) { Client c = new Client(); Target t = new Adapter(); t.handleReq();}
工作中使用的场景:
- 经常用来做旧系统改造和升级
- java.io.InputStreamReader(InputStream)
- java.io.OutputStreamWriter(OutputStream)
- ……等等
9、桥接模式
如果说某个系统能够从多个角度来进行分类,且每一种分类都可能会变化,那么我们需要做的就是讲这多个角度分离出来,使得他们能独立变化,减少他们之间的耦合,这个分离过程就使用了桥接模式。
桥接模式 就是将抽象部分和实现部分隔离开来,使得他们能够独立变化。桥接模式包含如下角色:
- Abstraction:抽象类
- RefinedAbstraction:扩充抽象类
- Implementor:实现类接口
- ConcreteImplementor:具体实现类
UML结构图:



//品牌- Implementor:实现类接口public interface Brand { void sale();}//ConcreteImplementor:具体实现类-联想品牌class Lenovo implements Brand { @Override public void sale() { System.out.println("销售联想电脑"); }}//ConcreteImplementor:具体实现类-Dell品牌class Dell implements Brand { @Override public void sale() { System.out.println("销售Dell电脑"); }}//ConcreteImplementor:具体实现类-神舟品牌class Shenzhou implements Brand { @Override public void sale() { System.out.println("销售神舟电脑"); }}
//Abstraction:抽象类-电脑类型的维度public class Computer2 { protected Brand brand; public Computer2(Brand b) { this.brand = b; } public void sale(){ brand.sale(); } }//RefinedAbstraction:扩充抽象类-类型扩展:台式class Desktop2 extends Computer2 { public Desktop2(Brand b) { super(b); } @Override public void sale() { super.sale(); System.out.println("销售台式机"); }}//RefinedAbstraction:扩充抽象类-类型扩展:笔记本class Laptop2 extends Computer2 { public Laptop2(Brand b) { super(b); } @Override public void sale() { super.sale(); System.out.println("销售笔记本"); }}
public class Client { public static void main(String[] args) { //销售联想的笔记本电脑 Computer2 c = new Laptop2(new Lenovo()); c.sale(); //销售神舟的台式机 Computer2 c2 = new Desktop2(new Shenzhou()); c2.sale(); }}
桥接模式总结:
- 桥接模式可以取代多层继承的方案。 多层继承违背了单一职责原则,复用性较差,类的个数也非常多。桥接模式可以极大的减少子类的个数,从而降低管理和维护的成本。
- 桥接模式极大的提高了系统可扩展性,在两个变化维度中任意扩展一个维度,都不需要修改原有的系统,符合开闭原则。
10、组合模式
组合模式 组合多个对象形成树形结构以表示“整体-部分”的结构层次。它定义了如何将容器对象和叶子对象进行递归组合,使得客户在使用的过程中无须进行区分,可以对他们进行一致的处理。
在使用组合模式中需要注意一点也是组合模式最关键的地方:叶子对象和组合对象实现相同的接口。组合模式包含如下角色:
- Component: 抽象构件,定义了叶子和容器构件的共同点
- Leaf: 叶子构件,无子节点
- Composite: 容器构件, 有容器特征,可以包含子节点
- Client: 客户类
UML结构图:

- 组合模式为处理树形结构提供了完美的解决方案,描述了如何将容器和叶子进行递归组合,使得用户在使用时可以一致性的对待容器和叶子。
- 当容器对象的指定方法被调用时,将遍历整个树形结构,寻找也包含这个方法的成员,并调用执行。其中,使用了递归调用的机制对整个结构进行处理。
文件管理案例:
抽象构件(Component):public interface AbstractFile { /**定义操作的方法*/ void operiation();}
叶子(Leaf):
public class ImageFile implements AbstractFile{ private String name; public ImageFile(String name) { super(); this.name = name; } @Override public void operiation() { System.out.println("图片文件:"+name+" 处理操作"); }}
public class TextFile implements AbstractFile{ private String name; public TextFile(String name) { super(); this.name = name; } @Override public void operiation() { System.out.println("文本文件:"+name+" 处理操作"); }}
public class VideoFile implements AbstractFile{ private String name; public VideoFile(String name) { super(); this.name = name; } @Override public void operiation() { System.out.println("视频文件:"+name+" 处理操作"); }}
Composite容器:
/** * Composite容器组件 * @author 波波烤鸭 */public class Folder implements AbstractFile{ private String name; // 定义容器,用来存储叶子节点 也就是存储文件 private Listlist = new ArrayList<>(); public Folder(String name) { super(); this.name = name; } public void add(AbstractFile file){ list.add(file); } public void remove(AbstractFile file){ list.remove(file); } public AbstractFile getChild(int index){ return list.get(index); } @Override public void operiation() { System.out.println("处理:"+name+"文件夹"); for (AbstractFile file : list) { file.operiation(); } }}
测试:
public static void main(String[] args) { AbstractFile textFile = new TextFile("readme.txt"); AbstractFile imageFile = new ImageFile("image.jpg"); AbstractFile videoFile = new VideoFile("video.mp4"); Folder f = new Folder("d:/tools"); f.add(videoFile); f.add(imageFile); f.add(textFile); Folder f1 = new Folder("d:/"); f1.add(videoFile); f1.add(textFile); f1.add(imageFile); f1.add(f); f1.operiation();}输出结果:-----------------------------------------------------处理:d:/文件夹视频文件:video.mp4 处理操作文本文件:text.txt 处理操作图片文件:image.jpg 处理操作处理:d:/tools文件夹视频文件:video.mp4 处理操作图片文件:image.jpg 处理操作文本文件:text.txt 处理操作
开发中的应用场景:
- 操作系统的资源管理器
- GUI中的容器层次图
- XML文件解析
- OA系统中,组织结构的处理
- Junit单元测试框架,底层设计就是典型的组合模式,TestCase(叶子)、TestUnite(容器)、Test接口(抽象)
11、装饰模式
装饰者模式 又称为包装模式(Wrapper),作用是用来动态的为一个对象增加新的功能。装饰模式是一种用于代替继承的技术,无须通过继承增加子类就能扩展对象的新功能。使用对象的关联关系代替继承关系,更加灵活,同时避免类型体系的快速膨胀。
装饰模式包含如下角色:
- Component: 抽象构件,真实对象和装饰对象有相同的接口,这样客户端对象就能够以与真实对象相同的方式同装饰对象交互。
- ConcreteComponent: 具体构件,IO流中的FileInputStream,FileOutputStream。
- Decorator: 抽象装饰类,持有一个抽象组件的引用,装饰对象接受所有客户端的请求,并把这些请求转发给真实的对象,这样就能在真实对象调用前后增加新的功能。
- ConcreteDecorator: 具体装饰类,负责给组件对象增加新的责任
UML结构图:


- Component抽象构件角色: • io流中的InputStream、OutputStream、Reader、Writer
- ConcreteComponent具体构件角色: • io流中的FileInputStream、FileOutputStream
- Decorator装饰角色: • 持有一个抽象构件的引用:io流中的FilterInputStream、FilterOutputStream
- ConcreteDecorator具体装饰角色: • 负责给构件对象增加新的责任。Io流中的BufferedOutputStream、BufferedInputStream等
实际使用场景:
- IO中输入流和输出流的设计
- Swing包中图形界面构件功能
- Servlet API 中提供了一个request对象的Decorator设计模式的默认实现类HttpServletRequestWrapper,HttpServletRequestWrapper类,增强了request对象的功能。
- Struts2中,request,response,session对象的处理
装饰模式和桥接模式的区别:
两个模式都是为了解决过多子类对象问题。但他们诱因不一样。桥接模式是对象自身现有机制沿着多个维度变化,是既有部分不稳定。装饰模式是为了增加新的功能。12、外观模式
我们都知道类与类之间的耦合越低,那么可复用性就越好,如果两个类不必彼此通信,那么就不要让这两个类发生直接的相互关系,如果需要调用里面的方法,可以通过第三者来转发调用。外观模式非常好的诠释了这段话。
外观模式 提供了一个统一的接口,用来访问子系统中的一群接口。它让一个应用程序中子系统间的相互依赖关系减少到了最少,它给子系统提供了一个简单、单一的屏障,客户通过这个屏障来与子系统进行通信。
通过使用外观模式,使得客户对子系统的引用变得简单了,实现了客户与子系统之间的松耦合。但是它违背了“开闭原则”,因为增加新的子系统可能需要修改外观类或客户端的源代码。
- 各种技术和框架中,都有外观模式的使用。
- JDBC封装后的,
- commons提供的DBUtils类,
- Hibernate提供的工具类、
- Spring JDBC工具
13、享元模式
在一个系统中对象会使得内存占用过多,特别是那些大量重复的对象,这就是对系统资源的极大浪费。享元模式对对象的重用提供了一种解决方案,它使用共享技术对相同或者相似对象实现重用。
享元模式 就是运行共享技术有效地支持大量细粒度对象的复用。系统使用少量对象,而且这些都比较相似,状态变化小,可以实现对象的多次复用。
这里有一点要注意:享元模式要求能够共享的对象必须是细粒度对象。享元模式通过共享技术使得系统中的对象个数大大减少了,同时享元模式使用了内部状态和外部状态。- 内部状态:可以共享,不会随环境变化而改变
- 外部状态:不可以共享,会随环境变化而改变
享元模式包含如下角色:
- Flyweight: 抽象享元类,通常是一个接口或抽象类,声明公共方法,这些方法可以向外界提供对象的内部状态,设置外部状态。
- ConcreteFlyweight: 具体享元类,为内部状态提供成员变量进行存储。
- UnsharedConcreteFlyweight: 非共享具体享元类,不能被共享的子类可以设计为非共享享元类。
- FlyweightFactory: 享元工厂类,创建并管理享元对象,享元池一般设计成键值对
UML结构图:



缺点 | 优点 |
---|---|
模式较复杂,使程序逻辑复杂化,为了节省内存,共享了内部状态,分离出外部状态,而读取外部状态使运行时间变长。用时间换取了空间。 | 极大减少内存中对象的数量相同或相似对象内存中只存一份,极大的节约资源,提高系统性能外部状态相对独立,不影响内部状态缺点。 |
二、行为型
14、责任链模式
职责链模式 描述的请求如何沿着对象所组成的链来传递的。它将对象组成一条链,发送者将请求发给链的第一个接收者,并且沿着这条链传递,直到有一个对象来处理它或者直到最后也没有对象处理而留在链末尾端。让多个对象都有可能接收请求,将这些对象连接成一条链,并且沿着这条链传递请求,直到有对象处理它为止,这就是职责链模式。
职责链模式包含如下角色:
- Handler: 抽象处理者
- ConcreteHandler: 具体处理者
- Client: 客户类
UML结构图:

案例理解:
公司里面,请假条的审批过程:- 如果请假天数小于3天,主任审批
- 如果请假天数大于等于3天,小于10天,经理审批
- 如果大于等于10天,小于30天,总经理审批
- 如果大于等于30天,提示拒绝
开发中常见的场景:
- Java中,异常机制就是一种责任链模式。一个try可以对应多个catch,当第一个catch不匹配类型,则自动跳到第二个catch.
- Javascript语言中,事件的冒泡和捕获机制。Java语言中,事件的处理采用观察者模式。
- Servlet开发中,过滤器的链式处理
- Struts2中,拦截器的调用也是典型的责任链模式
- ……等等
15、命令模式
有些时候我们想某个对象发送一个请求,但是我们并不知道该请求的具体接收者是谁,具体的处理过程是如何的,们只知道在程序运行中指定具体的请求接收者即可,对于这样将请求封装成对象的我们称之为命令模式。
命令模式 将请求封装成对象,以便使用不同的请求、队列或者日志来参数化其他对象。同时命令模式支持可撤销的操作。命令模式可以将请求的发送者和接收者之间实现完全的解耦。
命令模式(Command)的参与者:
- Client客户类,创建一个具体命令对象并设定它的接收者。
- Command:抽象命令类,声明执行操作的接口
- ConcreteCommand:具体命令类,将一个接收者对象绑定于一个动作, 调用接收者相应的操作,以实现Execute
- Invoker:调用者,要求该命令执行这个请求。
- Receiver:接收者,知道如何实现与执行一个请求相关的操作。任何类都可能作为一个接收者。
UML结构图:

- Struts2中,action的整个调用过程中就有命令模式。
- 数据库事务机制的底层实现
- 命令的撤销和恢复
- ……等等
16、解释器模式
解释器模式 描述了如何构成一个简单的语言解释器,主要应用在使用面向对象语言开发的编译器中。它描述了如何为简单的语言定义一个文法,如何在该语言中表示一个句子,以及如何解释这些句子。
解释器模式包含如下角色:
- AbstractExpression: 抽象表达式
- TerminalExpression: 终结符表达式
- NonterminalExpression: 非终结符表达式
- Context: 环境类
- Client: 客户类
UML结构图:

- EL表达式式的处理
- 正则表达式解释器
- SQL语法的解释器
- 数学表达式解析器
- ……等等
17、迭代器模式
对于迭代在编程过程中我们经常用到,能够游走于聚合内的每一个元素,同时还可以提供多种不同的遍历方式,这就是迭代器模式的设计动机。
迭代器模式 就是提供一种方法顺序访问一个聚合对象中的各个元素,而不是暴露其内部的表示。迭代器模式是将迭代元素的责任交给迭代器,而不是聚合对象,我们甚至在不需要知道该聚合对象的内部结构就可以实现该聚合对象的迭代。特点 | 说明 |
---|---|
聚合对象 | 存储数据 |
迭代器 | 遍历数据 |
迭代器模式包含如下角色:
- Iterator: 抽象迭代器
- ConcreteIterator: 具体迭代器
- Aggregate: 抽象聚合类
- ConcreteAggregate: 具体聚合类
UML结构图:

- JDK内置的迭代器(List/Set)
- ……等等
18、中介者模式
中介者模式 就是用一个中介对象来封装一系列的对象交互,中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。在中介者模式中,中介对象用来封装对象之间的关系,各个对象可以不需要知道具体的信息通过中介者对象就可以实现相互通信。它减少了对象之间的互相关系,提供了系统可复用性,简化了系统的结构。
本质:
解耦多个对象之间的交互关系。每个对象都持有中介者对象的引用,只跟中介者对象打交道。我们通过中介者对象统一管理这些交互关系。UML结构图:


开发中常见的场景:
- MVC模式(其中的C,控制器就是一个中介者对象。M和V都和他打交道) – 窗口游戏程序,窗口软件开发中窗口对象也是一个中介者对象
- 图形界面开发GUI中,多个组件之间的交互,可以通过引入一个中介者对象来解决,可以是整体的窗口对象或者DOM对象
- Java.lang.reflect.Method#invoke()
- ……等等
*外观模式、代理模式与中介者模式的简单理解及区别?
- 中介者模式:A,B、C、D之间的对话通过E来传达。A,B、C、D可以互相不认识(减少了A,B、C、D对象间的耦合)
- 代理模式:A要送B礼物,A,B互相不认识,那么A可以找C来帮它实现送礼物的愿望(封装了A对象)
- 外观模式:A和B都要实现送花,送巧克力的方法,那么我可以通过一个抽象类C实现送花送巧克力的方法(A和B都继承C)。(封装了A,B子类)
- 外观模式 和 中介者模式 的区别?
- 1、外观模式是结构型模式,中介者模式是行为型模式。
- 2、外观模式是对子系统提供统一的接口,中介者模式是用一个中介对象来封装一系列同事对象的交互行为。
- 3、外观模式协议是单向,中介者模式协议是双向。
- 4、外观模式所有的请求处理都委托给子系统完成,而中介者模式则由中心协调同事类和中心本身共同完成业务。 外观模式 和 代理模式 的区别?
- 1、代理模式中的代理角色和真实角色都继承于同一类。而外观模式是多个类的集合。
- 2、代理角色与真实角色接口相同,功能一致,代理角色实现的是真实角色的功能。外观者模式的子系统功能不同,根据用户不同需要与外观类统一配置。 代理模式 和 中介者模式 的区别?
- 1、代理模式是一对一,一个代理只能代表一个对象。中介者模式则是多对多,中介者的功能多样,客户也可以多个。
- 2、只能代理一方。如果PB是A的代理,那么C可以通过PB访问A,但是A不能通过PB访问B。对于中介者模式而言,A可以通过中介访问B,B也可以通过中介访问A。
19、备忘录模式
后悔药人人都想要,但是事实却是残酷的,根本就没有后悔药可买,但是也不仅如此,在软件的世界里就有后悔药!备忘录模式就是一种后悔药。
备忘录模式 就是在不破坏封装的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样可以在以后将对象恢复到原先保存的状态。它实现了对信息的封装,使得客户不需要关心状态保存的细节。保存就要消耗资源,所以备忘录模式的缺点就在于消耗资源。如果类的成员变量过多,势必会占用比较大的资源,而且每一次保存都会消耗一定的内存。使用场景:
- 录入大批人员资料。正在录入当前人资料时,发现上一个人录错了, 此时需要恢复上一个人的资料,再进行修改。
- Word文档编辑时,忽然电脑死机或断电,再打开时,可以看到word 提示你恢复到以前的文档
- 管理系统中,公文撤回功能。公文发送出去后,想撤回来
核心内容:
就是保存某个对象内部状态的拷贝,这样以后就可以将该对象恢复到原先的状态。备忘录模式包含如下角色:
- Originator: 原发器,负责创建一个备忘录Memento,用以记录当前时刻它的内部状态,并可使用备忘录恢复内部状态。
- Memento: 备忘录,负责存储Originator对象的内部状态,并可防止Originator以外的其他对象访问Memento。
- Caretaker: 负责人,负责保存好备忘录Memento。
UML结构图:

- 棋类游戏中的,悔棋
- 普通软件中的,撤销操作
- 数据库软件中的,事务管理中的,回滚操作
- Photoshop软件中的,历史记录
20、观察者模式
观察者模式 又称为发布/订阅(Publish/Subscribe)模式,在对象之间定义了一对多的依赖,这样一来,当一个对象改变状态,依赖它的对象会收到通知并自动更新
观察者模式包含如下角色:
- Subject: 目标
- ConcreteSubject: 具体目标
- Observer: 观察者
- ConcreteObserver: 具体观察者
UML结构图:

开发中的常用场景:
- 聊天室程序的,服务器转发给所有客户端
- 网络游戏(多人联机对战)场景中,服务器将客户端的状态进行分发 邮件订阅
- Servlet中,监听器的实现
- Android中,广播机制
- JDK的AWT中事件处理模型,基于观察者模式的委派事件模型(DelegationEventModel) • 事件源----------------目标对象 • 事件监听器------------观察者
- 京东商城中,群发某商品打折信息
- 工作号发布消息
- zookeeper底层架构实现原理
- MQ的消息通知机制
- ……等等
21、状态模式
在很多情况下我们对象的行为依赖于它的一个或者多个变化的属性,这些可变的属性我们称之为状态。
状态模式 就是允许对象在内部状态发生改变时改变它的行为,对象看起来好像修改了它的类。
在状态模式中我们可以减少大块的if…else语句,它是允许态转换逻辑与状态对象合成一体,但是减少if…else语句的代价就是会换来大量的类,所以状态模式势必会增加系统中类或者对象的个数。 同时状态模式是将所有与某个状态有关的行为放到一个类中,并且可以方便地增加新的状态,只需要改变对象状态即可改变对象的行为。但是这样就会导致系统的结构和实现都会比较复杂,如果使用不当就会导致程序的结构和代码混乱,不利于维护。状态模式包含如下角色:
- [x]Context: 环境类
- [x]State: 抽象状态类
- [x]ConcreteState: 具体状态类
UML结构图:

- 银行系统中账号状态的管理
- OA系统中公文状态的管理
- 酒店系统中,房间状态的管理
- 线程对象各状态之间的切换
- ……等等
22、策略模式
策略模式就是定义了算法族,分别封装起来,让他们之前可以互相转换,此模式然该算法的变化独立于使用算法的客户。
策略模式 它将这些解决问题的方法定义成一个算法群,每一个方法都对应着一个具体的算法,这里的一个算法我就称之为一个策略。虽然策略模式定义了算法,但是它并不提供算法的选择,即什么算法对于什么问题最合适这是策略模式所不关心的,所以对于策略的选择还是要客户端来做。客户必须要清楚的知道每个算法之间的区别和在什么时候什么地方使用什么策略是最合适的,
策略模式也非常完美的符合了“开闭原则”,用户可以在不修改原有系统的基础上选择算法或行为,也可以灵活地增加新的算法或行为。但是一个策略对应一个类将会是系统产生很多的策略类。策略模式包含如下角色:
- Context: 环境类
- Strategy: 抽象策略类
- ConcreteStrategy: 具体策略类
UML结构图:

public interface Strategy { public double getPrice(double standardPrice);}
ConcreteStrategy: 具体策略类,对应多种算法,可对算法进行扩展,符合开闭原则
public class NewCustomerFewStrategy implements Strategy { @Override public double getPrice(double standardPrice) { System.out.println("不打折,原价"); return standardPrice; }}
public class NewCustomerManyStrategy implements Strategy { @Override public double getPrice(double standardPrice) { System.out.println("打九折"); return standardPrice*0.9; }}
public class OldCustomerManyStrategy implements Strategy { @Override public double getPrice(double standardPrice) { System.out.println("打八折"); return standardPrice*0.8; }}
Context: 环境类,对外统一出口
/** * 负责和具体的策略类交互 * 这样的话,具体的算法和直接的客户端调用分离了,使得算法可以独立于客户端独立的变化。 * 如果使用spring的依赖注入功能,还可以通过配置文件,动态的注入不同策略对象,动态的切换不同的算法. * @author 精彩猿笔记 */public class Context { //当前采用的算法对象 private Strategy strategy; //可以通过构造器来注入 public Context(Strategy strategy) { super(); this.strategy = strategy; } //可以通过set方法来注入 public void setStrategy(Strategy strategy) { this.strategy = strategy; } public void pringPrice(double s){ System.out.println("您该报价:"+strategy.getPrice(s)); }}
测试
public static void main(String[] args) { Strategy s1 = new OldCustomerManyStrategy(); Context ctx = new Context(s1); ctx.pringPrice(1000);}输出:打八折您该报价:800
应用场景:
- 假如系统中有很多类,而他们的区别仅仅在于他们的行为不同。
- 一个系统需要动态地在几种算法中选择一种。
优点:
- 策略模式符合开闭原则。
- 避免使用多重条件转移语句,如 if…else…语句、switch 语句
- 使用策略模式可以提高算法的保密性和安全性。
缺点:
- 客户端必须知道所有的策略,并且自行决定使用哪一个策略类。
- 代码中会产生非常多策略类,增加维护难度。
开发中的使用场景:
- 比较器Comparator接口的compare()方法
- Arrays类的parallelSort()方法
- JAVASE中GUI编程中,布局管理
- Spring框架中,Resource 类
- javax.servlet.http.HttpServlet#service()
- ……等等
23、模板方法
有些时候我们做某几件事情的步骤都差不多,仅有那么一小点的不同,在软件开发的世界里同样如此,如果我们都将这些步骤都一一做的话,费时费力不讨好。所以我们可以将这些步骤分解、封装起来,然后利用继承的方式来继承即可,当然不同的可以自己重写实现嘛!这就是模板方法模式提供的解决方案。
模板方法模式 就是在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。
模板方法模式就是基于继承的代码复用技术的。模板方法模式就是基于继承的代码复用技术的。在模板方法模式中,我们可以将相同部分的代码放在父类中,而将不同的代码放入不同的子类中。也就是说我们需要声明一个抽象的父类,将部分逻辑以具体方法以及具体构造函数的形式实现,然后声明一些抽象方法让子类来实现剩余的逻辑,不同的子类可以以不同的方式来实现这些逻辑。模板方法模式包含如下角色:
- AbstractClass: 抽象类
- ConcreteClass: 具体子类
UML结构图:

//AbstractClass: 抽象类 public abstract class BankTemplateMethod { //具体方法 public void takeNumber(){ System.out.println("取号排队"); } //办理具体的业务 钩子方法:抽象方法,交给子类去实现 public abstract void transact(); //具体方法 public void evaluate(){ System.out.println("反馈评分"); } //模板方法 public final void process(){ this.takeNumber(); this.transact(); this.evaluate(); }}
public static void main(String[] args) { // 采用匿名内部类 BankTemplateMethod btm1 = new BankTemplateMethod() { @Override public void transact() { System.out.println("我要存钱¥¥¥¥¥¥ 一亿人民币"); } }; btm1.process(); System.out.println("---------------------------"); BankTemplateMethod btm2 = new BankTemplateMethod() { @Override public void transact() { System.out.println("我要理财¥¥¥¥¥¥ 我这里有2000万吨黄金"); } }; btm2.process();}输出:取号排队我要存钱¥¥¥¥¥¥ 一亿人民币反馈评分---------------------------取号排队我要理财¥¥¥¥¥¥ 我这里有2000万吨黄金反馈评分
开发中常见的场景:
- 数据库访问的封装
- Junit单元测试
- servlet中的doGet和doPost方法
- Hibernate中的模板程序
- Spring中的JdbcTemplate,HibernateTemplate等
- ……等等
24、访问者模式
在我们软件开发中我们可能会对同一个对象有不同的处理,如果我们都做分别的处理,将会产生灾难性的错误。对于这种问题,访问者模式提供了比较好的解决方案。访问者模式即表示一个作用于某对象结构中的各元素的操作,它使我们可以在不改变各元素的类的前提下定义作用于这些元素的新操作。
访问者模式 的目的是封装一些施加于某种数据结构元素之上的操作,为不同类型的元素提供多种访问操作方式,且可以在不修改原有系统的情况下增加新的操作方式。
同时我们还需要明确一点那就是访问者模式是适用于那些数据结构比较稳定的,因为他是将数据的操作与数据结构进行分离了。访问者模式包含如下角色:
- Vistor: 抽象访问者
- ConcreteVisitor: 具体访问者
- Element: 抽象元素
- ConcreteElement: 具体元素
- ObjectStructure: 对象结构
UML结构图:


开发中的场景:
- XML文档解析器设计
- 编译器的设计
- 复杂集合对象的处理
- ……等等
====================================================================
······
帮助他人,快乐自己,最后,感谢您的阅读! 所以如有纰漏或者建议,还请读者朋友们在评论区不吝指出!…知识是一种宝贵的资源和财富,益发掘,更益分享…
发表评论
最新留言
关于作者
