常见设计模式详解

1.代理模式(应用:一是创建多线程的方式之使用Runnable接口应用了静态代理;二是Spring AOP应用了动态代理)

参考文章:https://blog.csdn.net/can_chen/article/details/107827629
代理模式分为静态代理和动态代理,动态代理又分为基于接口实现的动态代理和基于子类实现的动态代理;在jdk源码中,很多底层的实现也是基于代理模式的,例如创建线程的方式之一实现Runnable接口就使用了静态代理模式,而Spring框架最为重要的AOP的实现是基于动态代理模式,可见,学习代理模式是我们能看懂底层源码实现原理的一个基础。

类加载阶段的双亲委派机制也是代理模式

关于静态代理模式:

  1. 要求代理对象和真实对象实现同一个接口
  2. 当真实对象过多,导致代码量翻倍,代码重用性不强,开发效率降低
  3. 而且静态代理模式要增加一个代理类的时候,要修改底层代码,违反了OCP原则
  4. 静态代理模式通过代理对象可以做很多真实对象做不了的事情,实现对真实对象的一个增强,同时真实对象也可以专注的去做一件事情

静态代理模式在jdk源码中的应用
实现Runnable接口创建线程的方式用到了静态代理模式
回顾一下步骤:先创建一个Runnable接口的实现类对象,然后创建一个Thread类,并将Runnable接口的实现类作为参数传递进去,然后调用Thread类对象的start方法开启多线程;
Runnable接口的实现类对象是真实对象,Thread类对象是代理对象,静态代理模式要求代理对象和真实对象继承同一个接口,Runnable接口的实现类对象和Thread类对象都继承了Runnable接口,所以完全符合静态代理模式

关于动态代理:
1.分类
基于接口的动态代理:使用JDK官方给我们提供好的一个Proxy类(java.lang.reflect)来动态的创建代理对象,创建方法是使用Proxy类中的newProxyInstance方法,该方法需要传递三个参数,一是ClassLoader类加载器,用于加载代理对象字节码的,和被代理对象使用相同的类加载器;二是Class[]字节码数组,用于让代理对象和被代理对象有相同方法;三是InvocationHandler,用于提供增强的代码,也就是让我们写如何代理;基于接口的动态代理弊端就是要求被代理类最少实现一个接口,如果没有则不能使用
基于子类的动态代理:需要依赖第三方cglib库,如果是maven项目则导入cglib相关的依赖即可,基于子类的动态代理要求被代理类不能是最终类
1.好处

  • 不修改原有代码的基础上对方法进行增强
  • 解决了静态代理一个真实对象必须有一个代理对象,导致类数量大大增加的缺点,一个动态代理类可以代理多个类,只要实现的是同一个接口即可

动态代理的应用
Spring的AOP(面向切面编程)就运用到了动态代理模式,换言之,AOP的实现方式就是使用动态代理技术,AOP就是在不改变原有代码的基础上,可以将要增强的代码横切进去,例如要给原来的业务逻辑代码增加打印日志或者事务管理等;简单的说它就是把我们程序重复的代码抽取出来,在需要执行的时候,使用动态代理的技术,在不修改源码的 基础上,对我们的已有方法进行增强

2.适配器模式(应用:一是创建多线程的方式之使用Callable接口;二是SpringMVC中的HandleAdapter)

参考文章:https://blog.csdn.net/can_chen/article/details/106968769
工作原理
1.将一个类的接口转换成客户希望的另外一个接口,Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作
2.从用户的角度看是看不到被适配者的,是解耦的
适配器模式属于结构型模式,主要分三类:类适配器模式、对象适配器模式、接口适配器模式

类适配器模式
假设我们现在有这么一个需求:需要给我们的手机充电,手机充电只能使用5V的电压,但是我们家里的插座却是220V的电压,也就是说手机无法直接使用220V的插座进行充电,那么现在就需要一个适配器来进行5V电压和220V电压的转换,这个适配器就是我们平时所用的充电器。
类图
在这里插入图片描述
通过上面的例子,我们再来理解一下适配器模式的工作原理,就非常清晰了;适配器模式的工作原理是说将一个类的接口转换成客户希望的另外一个接口,代入上面的例子中,也就是说把已存在的220V这个类转换成手机充电需要的5V接口了;另外,Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作;即指的是由于手机不能直接与220V的电压一起工作,但是有了适配器之后,就使得手机可以与220V电压一起工作了(间接)
关于类适配器

  • Java 类是单继承机制,有一定局限性;因为类适配器需要继承被适配器类, 这就要求目标必须是接口了。
  • 被适配器类的方法在 Adapter 中都会暴露出来,也增加了使用的成本。
  • 由于适配器继承了被适配器类,所以它可以根据需求重写被适配器类的方法,使得适配器的灵活性增强了。

对象适配器
类图如下
在这里插入图片描述
关于对象适配器:

  • 对象适配器和类适配器其实算是同一种思想,只不过实现方式不同。根据合成复用原则,使用组合替代继承,
    所以它解决了类适配器必须继承被适配器类的局限性问题,也不再要求目标必须是接口。
  • 使用成本更低,更灵活。

接口适配器模式
核心思路:当不需要全部实现接口提供的方法时,可先设计一个抽象类实现接口,并为该接口中每个方法提供一个默认实现(空方法),那么该抽象类的子类可有选择地覆盖父类的某些方法来实现需求;适用于一个接口不想使用其所有的方法的情况。
为什么要使用适配器接口呢?试想,如果我们的目标接口中有多个方法需要进行适配,那么每一个适配器去实现这个目标接口的话都需要实现接口中的所有方法,但是这个适配器类只需要针对某一个方法进行适配,而每次都要实现所有方法,即使是空实现,也会使代码变得非常冗余,所以才需要适配器接口;让这个适配器接口去实现目标接口所有方法(空实现),而具体的适配器再去继承适配器接口,再针对需要的方法去重写即可!
类图如下在这里插入图片描述

3.工厂模式(应用:一是Spring IOC;二是Mybatis中的SqlSessionFactory就用到了简单工厂模式)

参考文章:https://blog.csdn.net/can_chen/article/details/105924115
工厂模式(Factory Pattern)属于创建型模式,分为简单工厂模式、工厂方法模式和抽象工厂模式;有的地方是把工厂模式和抽象工厂模式区分开来,工厂模式只包括简单工厂模式、工厂方法模式;其实怎么划分因人而异,最主要的是要能够知道这几种模式的思想,并且了解他们的异同点,知道在哪种场景下适合用哪种模式即可!
我们之前创建一个对象,都是通过new来直接创建,例如下面的代码,这样做的缺点就是客户端要了解手机接口下的所有实现类,只有了解了接口和实现类才能去创建具体的手机对象,并且,要是创建手机对象需要传递很多参数的话,那么会给客户端带来很多麻烦。
1. 简单工厂模式
类图:
在这里插入图片描述
手机接口、小米手机、华为手机的代码跟上面的例子一致,增加了工厂类,并且对客户端代码进行修改
对简单工厂模式的分析: 简单工厂模式把创建对象的能力交给工厂,客户端要想创建一个对象无需知道对象创建的内部细节,只需要声明对象的类型,通过工厂来获取我们想要的对象。工厂模式的缺点是违反ocp原则,例如我们要增加一个产品,那么需要修改原有的代码,也就是需要修改工厂类的判断逻辑,增加一层判断。

2. 工厂方法模式
类图:
在这里插入图片描述
手机接口、小米手机、华为手机的代码跟上面的例子一致,增加了抽象工厂、生产小米手机的工厂、生产华为手机的工厂,并且对客户端代码进行修改
对工厂方法模式的分析: 工厂方法模式解决了简单工厂模式出现的弊端,也就是满足了ocp原则,例如我们要增加一个产品,步骤就是编写一个产品类去实现产品接口,另外还需要编写一个该产品的工厂去实现抽象工厂,而不需要修改原有的代码,也就是说,工厂方法模式可以将对象的创建和使用解耦,缺点就是每扩展一个产品就需要创建该产品类以及生产该产品的工厂类,会大大增加类的数量,结构复杂。

简单工厂模式和工厂方法模式的对比
如果从结构复杂度、代码复杂度、编程复杂度和管理上的复杂度来考虑,我们会使用简单工厂模式,而如果从设计原则来说,譬如我们不能违反ocp原则,那么就得选择工厂方法模式;虽然工厂方法模式更符合设计模式的思想和原则,但由于简单工厂模式比较简单,所以在实际业务中,会更多的使用简单工厂模式。

3. 抽象工厂模式
类图:
在这里插入图片描述

手机接口、小米手机、华为手机的代码跟上面的例子一致,增加了路由器接口、小米路由器、华为路由器、抽象工厂、生产小米系列产品的工厂、生产华为系列产品的工厂,并且对客户端代码进行修改
对抽象工厂模式的分析: 抽象工厂模式提供了一个创建一系列相关或者相互依赖对象的接口,无需指定它们具体的类,优点是将一个系列的产品统一到一起创建;缺点是规定了所有可能被创建的产品集合,产品族中扩展新的产品困难。

工厂方法模式与抽象工厂模式的区别
关于二者的区别,我个人看过较好的解释是从产品族和产品等级来进行阐述,下面附上一张从网上拷贝过来的关于产品族和产品等级的图片
在这里插入图片描述

工厂方法模式只能生产一种类型的产品,但是支持同等级产品的扩展例如上面的手机工厂,只能生产手机,而如果要生产其他产品,例如路由器,那么需要修改工厂类,违反ocp,如果进行的是同等级产品的扩展,例如要生产小米手机、华为手机等,不需要修改工厂类,只需要增加生产该等级产品的工厂子类即可,符合ocp。
而抽象工厂模式支持的是同一系列产品的创建,也就是同一产品族,例如小米系列产品,华为系列产品,抽象工厂模式解决了工厂方法模式只能创建一种类型产品的弊端,并且支持的是同一产品族的扩展,也就是说,抽象工厂可以扩展同一类产品,例如可以在小米产品族,华为产品族的基础上再加多一个苹果产品族,但是不可以在一个产品族里再加产品,因为这样子要修改工厂类的内部代码。而工厂方法模式扩展的是统一产品等级,比如可以在小米手机,华为手机的基础上再增加一个魅族手机。

应用场景

  • JDK中Calendar的getInstance方法就用到了简单工厂模式
  • Spring中的IOC容器也用到了工厂模式,把创建对象的能力都交给IOC容器,换句话说,我们要创建某个对象实例,只需要从spring工厂中拿即可。

4.单例模式(一般会需要手撕单例模式的代码,主要谈DCL模式关于指令重排的问题,以及反射可以破坏除枚举以外的几种方式;单例模式的应用有:一是Spring Bean的作用域默认就是使用单例模式;二是Mybatis中的ErrorContext类也使用了单例模式,这个类用于记录线程执行环境的错误信息)

参考文章:https://blog.csdn.net/can_chen/article/details/105049999
所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且对该类只提供一个取得其对象实例的方法(公有的静态方法)
优点:由于单例模式只生成一个实例,减少了系统性能的开销

单例模式的常见场景

  • windows的任务管理器是使用单例模式,比如我打开了一个任务管理器,再想打开任务管理器,它不会再弹出一个来,而保证桌面只出现一个任务管理器
  • 在Spring中,每个bean默认就是单例的(可以通过设置bean的作用域来使它变成多例的)
  • 在Servlet编程中,每个Servlet就是单例的
  • 数据库连接池的设计一般也是单例的

单例模式的八种方式
(其实是五种,这里写成八种只是为了细分出来)
1.饿汉式(静态变量)***
在这里插入图片描述

优缺点:

  • 优点:这种写法比较简单,就是在类装载的时候就完成实例化。避免了线程同步问题。
  • 缺点:在类装载的时候就完成实例化,没有达到 Lazy Loading 的效果。如果从始至终从未使用过这个实例,则会造成内存的浪费

这种方式基于 classloder 机制避免了多线程的同步问题,不过,instance 在类装载时就实例化,在单例模式中大多数都是调用 getInstance 方法,但是导致类装载的原因有很多种,因此不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化 instance 就没有达到 lazy loading 的效果
结论:这种单例模式不建议用,可能造成内存浪费

2.饿汉式(静态代码块)
在这里插入图片描述
优缺点:
这种方式和饿汉式(静态变量)其实类似,只不过将类实例化的过程放在了静态代码块中,也是在类装载的时候,就执行静态代码块中的代码,初始化类的实例。优缺点和饿汉式(静态变量)是一样的。

3.懒汉式(线程不安全)***
在这里插入图片描述
优缺点:

  • 起到了 Lazy Loading 的效果,但是只能在单线程下使用。
  • 如果在多线程下,一个线程进入了 if (instance ==null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例。所以在多线程环境下不可使用这种方式。
    结论:在实际开发中,不要使用这种方式。

4.懒汉式(线程安全,同步方法,效率低)
在这里插入图片描述
优缺点:

  • 解决了线程安全问题
  • 效率太低了,每个线程在想获得类的实例时候,执行getInstance()方法都要进行同步。而其实这个方法只执行一次实例化代码就够了,后面的想获得该类实例,直接 return就行了。方法进行同步效率太低。
    结论:在实际开发中,不推荐使用这种方式

5.懒汉式(线程不安全,同步代码块,效率高)
在这里插入图片描述
优缺点:

  • 使用同步代码块解决了效率问题。
  • 是线程不安全的,如果在多线程下,一个线程进入了 if (instance ==
    null)判断语句块,进而进入同步代码块,还未执行完毕,另一线程也进入if块,在同步代码块外面等待前一个线程执行完再进入,同样会产生多个实例。所以在多线程环境下不可使用这种方式。
    结论:在实际开发中,不要使用这种方式。

6.双重检查(Double-Check)/双重锁懒汉模式(Double-Check-Lock)/DCL***
在这里插入图片描述
优缺点:
1.Double-Check 概念是多线程开发中常使用到的,如代码中所示,我们进行了两次 if (instance == null)检查,这样就可以保证线程安全了。这样,实例化代码只用执行一次,后面再次访问时,判断 if (instance == null),直接 return 实例化对象,也避免的反复进行方法同步。
2.线程安全;延迟加载;效率较高
3.注意:由于jvm存在乱序执行功能,DCL也会出现线程不安全的情况。具体分析如下:instance = new Singleton6();这个步骤,其实在jvm里面的执行分为三步:1.在堆内存开辟内存空间。2.在堆内存中实例化Singleton6里面的各个参数(执行构造方法)。3.把对象指向堆内存空间。由于jvm存在乱序执行功能,所以可能在2还没执行时就先执行了3,如果此时再被切换到线程B上,由于执行了3,instance已经非空了,会被直接拿出来用,这样的话,就会出现异常。这个就是著名的DCL失效问题。不过在JDK1.5之后,官方也发现了这个问题,故而具体化了volatile,即在JDK1.6及以后,只要定义为private volatile static Singleton6 instance = null;就可解决DCL失效问题。volatile确保instance每次均自己在主内存中读取,这样虽然会牺牲一点效率,但也无伤大雅。
4.对于jvm乱序使得双重检查方式也可能出现错误,我的理解是:假设第一个线程已经执行到instance = new Singleton6();这行代码了,但是它在完成这行代码的三个操作时发生了乱序,即分配完内存后就直接把引用指向该对象,还没有执行构造方法;此时另一个线程来到了最外面的“if(instance==null)”这行代码,发现instance不为null,就直接return instance了,那么使用这个instance就会出现错误。

7.静态内部类(饿汉式的改进)***
在这里插入图片描述
优缺点:
1.这种方式采用了类装载的机制来保证初始化实例时只有一个线程。
2.加载一个类时,不管其内部类是静态的还是非静态,内部类不会同时被加载。一个类被加载,当且仅当其某个静态成员(静态域、构造器、静态方法等)被调用时发生。 因此静态内部类方式在 Singleton7 类被装载时并不会立即实例化,而是在需要实例化时,调用 getInstance 方法,才会装载 SingletonInstance 类,从而完成 Singleton7 的实例化。
3.类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM 帮助我们保证了线程的安全性,在类进行初始化时,别的线程是无法进入的。
4.优点:避免了线程不安全,利用静态内部类特点实现延迟加载,效率高
5.结论:推荐使用

总结
饿汉式(线程安全,效率高,不能延时加载)
懒汉式(线程安全,效率低,可以延时加载)
DCL懒汉式(由于JVM底层内部模型原因,可能会出现问题,不建议使用)
静态内部类(线程安全,效率高,可以延时加载)
枚举单例(线程安全,效率高,不能延时加载)
注意:
一般我们说的饿汉式是指采用静态变量方式的饿汉式;
一般我们说的懒汉式是指采用同步方法的懒汉式;
面试中比较常考的就饿汉式,懒汉式,双重检查,静态内部类几种

5.装饰器模式(应用:JAVA的IO流就用到了装饰器模式)

参考文章:https://blog.csdn.net/can_chen/article/details/105786680
手抓饼问题:我们可以单点一个手抓饼,也可以给手抓饼添加一些配料,比如可以添加鸡蛋、培根、火腿等,有了这些配料之后,那么我们可以点一个鸡蛋手抓饼、鸡蛋火腿手抓饼、还可以点一个加了两个鸡蛋一个火腿的手抓饼……,组合有很多种,另外,我们还要能够计算出每个手抓饼的价格!
第一种方案:我首先想到的设计一个抽象父类Food(食物类),该抽象类包含一个抽象方法cost,用于计算手抓饼的价格,然后再根据客户的需求,创建各种子类去继承父类,实现cost方法,比如可以创建手抓饼类,鸡蛋手抓饼类,鸡蛋火腿手抓饼类……,有人可能会说,这样能够满足ocp原则,便于扩展,无可否认,这种方案确实满足了ocp原则,客户要点一个培根火腿手抓饼,那么我们就去创建一个培根火腿手抓饼子类去继承Food类,便于扩展,而且不会修改原来的代码;但是我们应该清楚,客户的需求有千万种,也就是说手抓饼的组合有千万种,如果客户每来一个需求我们就去创建一个子类,就会出现类爆炸,代价岂不是太大了?
方案一类图:
在这里插入图片描述

我们换一种方案:还是设计一个抽象父类(Food),让手抓饼类和调料类分别去继承Food类,这里我们将调料类设计成抽象类,该类聚合了Food类,cost方法提供空实现,让具体的鸡蛋类、培根类、火腿类去重写即可。
类图:
在这里插入图片描述
装饰者模式就是动态的将新功能附加到对象上。在对象功能扩展方面,它比继承更有弹性, 装饰者模式也体现了开闭原则(ocp)
之前还在另一篇博客上看到关于装饰者模式的另一个定义:装饰模式是在不必改变原类文件和使用继承的情况下,动态的扩展一个对象的功能。它是通过创建一个包装对象,也就是装饰来包裹真实的对象。

装饰者模式类图:
在这里插入图片描述

上面例子中,手抓饼类就是被装饰者,而调料类就是装饰者,被装饰者被聚合到装饰者类里面,可以动态的将各种调料附加到被装饰者上,如果客户有新的需求或者要添加新的调料,都非常的方便,无需修改原来的代码,扩展性很好!
装饰者模式的思想就是使用了继承+聚合/组合,核心就是将被装饰者聚合到装饰者类里面,有人可能会问为什么不将装饰者聚合到被装饰者里面,例如上面的例子中就是将调料类装饰到手抓饼类,这样做的话被装饰者类里面需要进行逻辑判断,针对不同的调料执行不同的方法,那么如果增加新的调料需要修改原来的代码,违反ocp,不可取。
在生活中,我们能举出很多用到装饰者模式的例子:

  • 奶茶问题:我们可以单点一杯奶茶,也可以给奶茶加珍珠、加椰果、加奶盖……
  • 咖啡问题:我们可以点单品咖啡,也可以给咖啡加牛奶、加巧克力、加豆浆……
  • 快递问题:我们可以给我们的物件加上报纸填充、塑料泡沫、纸板、木板等……

装饰者模式在jdk源码中的体现
JAVA的IO就用到了装饰者模式,我们先附上类图:
在这里插入图片描述
1.InputStream 是抽象类, 类似我们前面例子中的Food
2.FileInputStream、StringBufferInputStream、ByteArrayInputStream是 InputStream 子类,类似我们前面的SZB,是被装饰者
3.FilterInputStream 是 InputStream 子类:类似我们前面的tiaoliao,也就是装饰者抽象类,类内部聚合了InputStream类,源码是protected volatile InputStream in
4.BufferedInputStream、DataInputStream 是 FilterInputStream的子类,具体的装饰者,类似前面的Egg、peigen、huotui
从java的io结构中,可以充分体会到装饰器模式的灵活运用,我们创建的一个FileInputstream对象,可以使用各种装饰器让它具有不同的特别的功能,比如我们可以把它装饰成BufferedInputStream,它提供我们mark,reset的功能,这正是动态扩展一个类的功能的最佳体现。
//普通的FileInputStream对象
InputStream inputStream = new FileInputStream(filePath);//装饰成 BufferedInputStream,提供mark、reset功能
BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream);//再进行一层包装,装饰成DataInputStream
DataInputStream dataInputStream = new DataInputStream(bufferedInputStream);

6.装饰器模式和代理模式的区别?

参考文章:https://www.cnblogs.com/yanggb/p/10952843.html
对装饰器模式来说,装饰者(Decorator)和被装饰者(Decoratee)都实现一个接口。对代理模式来说,代理类(Proxy Class)和真实处理的类(Real Class)都实现同一个接口。此外,不论我们使用哪一个模式,都可以很容易地在真实对象的方法前面或者后面加上自定义的方法。
在上面的例子中,装饰器模式是使用的调用者从外部传入的被装饰对象(coffee),调用者只想要你把他给你的对象装饰(加强)一下。而代理模式使用的是代理对象在自己的构造方法里面new的一个被代理的对象,不是调用者传入的。调用者不知道你找了其他人,他也不关心这些事,只要你把事情做对了即可。
装饰器模式关注于在一个对象上动态地添加方法,而代理模式关注于控制对对象的访问。换句话说,用代理模式,代理类可以对它的客户隐藏一个对象的具体信息。因此当使用代理模式的时候,我们常常在一个代理类中创建一个对象的实例;当使用装饰器模式的时候,我们通常的做法是将原始对象作为一个参数传给装饰器的构造器。
装饰器模式和代理模式的使用场景不一样,比如IO流使用的是装饰者模式,可以层层增加功能。而代理模式则一般是用于增加特殊的功能,有些动态代理不支持多层嵌套。
代理和装饰其实从另一个角度更容易去理解两个模式的区别:代理更多的是强调对对象的访问控制,比如说,访问A对象的查询功能时,访问B对象的更新功能时,访问C对象的删除功能时,都需要判断对象是否登陆,那么我需要将判断用户是否登陆的功能抽提出来,并对A对象、B对象和C对象进行代理,使访问它们时都需要去判断用户是否登陆,简单地说就是将某个控制访问权限应用到多个对象上;而装饰器更多的强调给对象加强功能,比如说要给只会唱歌的A对象添加跳舞功能,添加说唱功能等,简单地说就是将多个功能附加在一个对象上。
所以,代理模式注重的是对对象的某一功能的流程把控和辅助,它可以控制对象做某些事,重心是为了借用对象的功能完成某一流程,而非对象功能如何。而装饰模式注重的是对对象功能的扩展,不关心外界如何调用,只注重对对象功能加强,装饰后还是对象本身。
总结
对于代理类,如何调用对象的某一功能是思考重点,而不需要兼顾对象的所有功能;对于装饰类,如何扩展对象的某一功能是思考重点,同时也需要兼顾对象的其他功能,因为再怎么装饰,本质也是对象本身,要担负起对象应有的职责,被装饰者的职责一旦增加,作为装饰类也需要有相应的扩展,必然会造成编码的负担。

7.策略模式(应用:Arrays类的sort方法就用到了策略模式,Comparator接口就是一个策略接口,将排序方法定义成一个策略,用户可以自定义排序策略,可以是升序也可以是降序)

参考文章:https://blog.csdn.net/can_chen/article/details/106745298

8.观察者模式与发布订阅模式的区别?(JDK源码已经为我们提供好了一套观察者模式,Observer就是观察者接口,Observable类是被观察者,是一个具体类,提供了一个存放所有观察者角色的集合,并且也提供了添加观察者、移除观察者、通知观察者等方法)

参考文章: https://blog.csdn.net/hf872914334/article/details/88899326
观察者模式(Observer Pattern)
观察者模式定义了对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知,并自动更新。观察者模式属于行为型模式,行为型模式关注的是对象之间的通讯,观察者模式就是观察者和被观察者之间的通讯。
观察者模式有一个别名叫“发布-订阅模式”,或者说是“订阅-发布模式”,订阅者和订阅目标是联系在一起的,当订阅目标发生改变时,逐个通知订阅者。我们可以用报纸期刊的订阅来形象的说明,当你订阅了一份报纸,每天都会有一份最新的报纸送到你手上,有多少人订阅报纸,报社就会发多少份报纸,报社和订报纸的客户就是上面文章开头所说的“一对多”的依赖关系。

发布订阅模式(Pub-Sub Pattern)
其实24种基本的设计模式中并没有发布订阅模式,上面也说了,他只是观察者模式的一个别称。
但是经过时间的沉淀,似乎他已经强大了起来,已经独立于观察者模式,成为另外一种不同的设计模式。
在现在的发布订阅模式中,称为发布者的消息发送者不会将消息直接发送给订阅者,这意味着发布者和订阅者不知道彼此的存在。在发布者和订阅者之间存在第三个组件,称为消息代理或调度中心或中间件,它维持着发布者和订阅者之间的联系,过滤所有发布者传入的消息并相应地分发它们给订阅者。
举一个例子,你在微博上关注了A,同时其他很多人也关注了A,那么当A发布动态的时候,微博就会为你们推送这条动态。A就是发布者,你是订阅者,微博就是调度中心,你和A是没有直接的消息往来的,全是通过微博来协调的(你的关注,A的发布动态)。

观察者模式和发布订阅模式有什么区别?
我们先来看下这两个模式的实现结构:
在这里插入图片描述

观察者模式: 观察者(Observer)直接订阅(Subscribe)主题(Subject),而当主题被激活的时候,会触发(Fire Event)观察者里的事件。
发布订阅模式: 订阅者(Subscriber)把自己想订阅的事件注册(Subscribe)到调度中心(Topic),当发布者(Publisher)发布该事件(Publish topic)到调度中心,也就是该事件触发时,由调度中心统一调度(Fire Event)订阅者注册到调度中心的处理代码。
观察者模式和发布订阅模式最大的区别就是发布订阅模式有个事件调度中心。
观察者模式由具体目标调度,每个被订阅的目标里面都需要有对观察者的处理,这种处理方式比较直接粗暴,但是会造成代码的冗余。
而发布订阅模式中统一由调度中心进行处理,订阅者和发布者互不干扰,消除了发布者和订阅者之间的依赖 。这样一方面实现了解耦,还有就是可以实现更细粒度的一些控制。比如发布者发布了很多消息,但是不想所有的订阅者都接收到,就可以在调度中心做一些处理,类似于权限控制之类的。还可以做一些节流操作。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值