总结一下各个设计模式的特点,作为面试时的快速回忆和演讲计划。
种类
设计模式有23种。 大类分为创建型、结构型、动作型。
创建型有:
- 简单工厂(不是设计模式,但其是工厂方法和抽象工厂的基础)
- 工厂方法模式
- 抽象工厂模式
- 建造者模式
- 单例模式
- 原型模式
结构型有:
- 组合设计模式
- 装饰设计模式
- 代理模式
- 桥接模式
- 适配器模式
- 外观(门面)设计模式
- 享元模式
动作型(行为型)有:
- 备忘录模式
- 观察者模式
- 策略模式
- 命令模式
- 中介者模式
- 迭代器模式
- 状态模式
- 模板方法模式
- 访问者模式
- 责任链模式
- 解释器模式
结构型记忆口诀:组装大桥死外乡(组装代桥适外享)
动作型记忆口诀:被观测命中碟状磨坊,咋解?(备观策命中迭状模访,责解)
创建型
简单工厂
简单工厂模式,又叫做静态工厂模式,其并不是严格意义上的设计模式,但它却是工厂方法和抽象工厂设计模式的基础。
简单来说简单工厂模式就是一个工厂类,向其工厂方法传入一个名称,获取一个具体对象,获取的对象实现一个抽象接口。
日志框架LogFactory是一个典型的简单工厂模式的应用。
简单工厂的最大缺点就是如果新增具体类,则需要修改工厂方法,不符合开闭原则。
所以实际使用中,常使用反射来进行优化,JDBC是一个典型优化例子,我们经常写 “Class.forName” 来注册驱动,然后使用 “DriverManager.getConnection” 来获取连接。
工厂方法
由简单工厂模式衍生出工厂方法模式。
工厂方法模式的用意是定义一个创建产品对象的工厂接口,将实际创建工作推迟到子类中。
与简单工厂不同的是,工厂方法有4个角色:工厂接口、工厂实现、业务接口、业务实现。
每一个“工厂实现”又是一个简单工厂。比如“业务实现”有高级管理者、中级管理者、高级员工、普通员工。那么“员工工厂”这个工厂实现的方法就可以传入一个type来决定得到高级员工对象还是普通员工对象。
抽象工厂
一般说抽象工厂模式和工厂方法模式的区别是,工厂方法模式面对的是一个产品等级结构,而抽象工厂模式面对的是多个产品等级结构。
这里还涉及产品等级和产品族的问题。比如AMD和Intel的cpu属于同一个产品等级,Intel的主板、Cpu、芯片组以及AMD的主板、Cpu、芯片组属于2个产品族。
那么可以说,抽象工厂相比于工厂方法模式,其每一个具体的工厂都负责同一个产品族的所有产品等级的创建。比如同时创建好AMD的cpu、主板、芯片组、内存对象等。
在实践中,我们可以创建 一个业务引擎 或者说业务类来完成所需的处理流程,向这个业务类中传入我们指定的 具体工厂,由这个具体工厂提供流程中所需的同一产品族下的所有产品等级对象。
但是抽象工厂的缺点也很明显,如果需要给整个产品族添加一个新的产品,那么就需要修改抽象工厂接口,这样就会导致修改所有的工厂实现类。
java.sql.Connection是一个典型的抽象工厂应用,其根据不同的数据库返回不同的Statement和PerpareStatment,两者是同一个产品族。
单例
单例模式是确保一个类只有一个实例,自行创建并向其他对象提供这个实例。
实现单例有懒汉和饿汉两种方式,顾名思义,饿汉式就是使用前就创建好对象,懒汉式就是需要使用时才去创建单例对象。
型的实现单例设计模式的方式有双重检查加锁、私有静态内部类方式、枚举。
双重检查加锁方式主要要注意使用volatile修饰单例对象引用,保证创建过程中的可见性。
类级内部类也就是有static修饰的内部类,和外部类不存在依赖关系,当于其外部类的成员,只有在第一次被使用的时候才被会装载。我们可以将单例对象作为类级内部类中的一个静态成员,那么当第一次读取单例对象时,这个内部类才被装载,又由于单例对象处于静态的域中,只会被jvm初始化一次,保证了线程的安全性。
不过,上述方法存在序列化和反序列化后破坏单例的情况,解决方式是在单例类中加上“readResolve”方法,因为反序列化时“ObjectInputStream”会去判断是否有这个方法并调用。
另外,还有反射破坏单例的方式。懒汉式无法防御反射破坏。
单元素的枚举似乎是单例模式的最佳实现方法。反编译后可知枚举其实是使用静态代码块来初始化的。
另外,因为“ObjectInoutStream” 通过class和枚举的名称(name)获取唯一的Enum,并且反射类中的“Constructor”写死了不允许ENUM类进行反射操作。
spring中的AbstractFactoryBean中的getObject是单例的一个应用,其会先调用“isSingleton”方法判断,如果是,则返回单例对象。
建造者
建造者模式中包含四种角色builder接口、builder实现、director类也就是导演和具体产品。
一般来说,导演类负责和客户打交道,然后导演去调用具体的建造者。每一种产品包含众多的“零件”,每一种产品也对应一个具体的建造者。
不管如何变化,建造模式都存在两个部分,一个部分是零件构造和产品装配,另一个部分是整体构建的算法。
经常使用的StringBuilder便是建造者模式的一个例子。
原型
根据实现的不同可以分为简单原型模式和登记原型模式。
简单原型模式通常有三个角色,客户端负责发起创建对象的请求,抽象原型负责给出所需接口,一般可以是接口或者抽象类,其中一般有克隆自身的方法,以及还有具体原型对象。
登记原型模式比简单模式多了一个原型管理类,作用是创建具体原型并记录每一个被创建的对象。我们可以通过这个管理对象新增、获取和删除具体原型对象。
不过要注意浅克隆和深克隆的问题。如果一个对象中存在引用成员的话,那么就需要使用序列化的方式来实现深度克隆。
这个设计模式对于简化相似对象的创建过程有帮助。
当然,要防止克隆破坏单例的情况,一个是不让单例实现Cloneable接口,二是可以覆盖clone方法,并在其中调用“getInstance”方法。
JDK中的ArrayList和HashMap是原型模式的应用,其都实现了自身的clone方法。
结构型
适配器
将一个类的接口变换成客户端期待的另一个接口,让原本接口不匹配的两个类能互通。
分为类适配器和对象适配器。
类适配器有三个角色,adapter继承adaptee并实现target接口,因为是类适配器,所以target不能是类。
对象适配器角色相同,但是和类适配器不同的是adapter并不是继承adaptee,而是委派关系。
一个是继承,一个是组合的关系。
一般来说适配器模式不是软件设计或开发初期中所使用的,是随着后续和不同平台交互或维护过程中要采取的解决方案的一种。
实际应用中有很多例子,一般以Adapter为结尾来命名,比如以spring中的AdvisrAdapter。
组合
组合设计模式在应用上可以让使用者将整体和部分当成同一对象进行处理,比如将文件和文件夹当做同一种对象同等对待。
组合类型即是将继承相同抽象类或实现相同接口的类组合成为树状结构的一种设计模式。
这个设计模式一般有一个接口或抽象类定义公共行为,用来管理所有子对象。两个实现,一个叶子构建类,一个树枝构建类。然后根据所实现的接口不同分为安全式和透明式。
安全式的特点是树枝和叶子构建类实现接口,然后管理聚集的方法只出现在树枝构建类中。这样就让客户端不会误操作,去树叶节点操作增删改查。
透明式的特点是树枝和叶子构建类继承抽象类,抽象类中的增删改查方法为缺省方法,可以在方法体中抛出异常来表示不支持此类操作,然后树枝构建类覆盖增删改查方法,而叶子构建类不进行覆盖。
实际应用中,可以使用访问者模式来访问组合模式中的递归结构。
mybatis中的SqlNode是组合设计模式的实现之一。
装饰(包装)
也叫包装设计模式,在对客户端透明的前提下扩展功能,作为继承的一个替代方案。
最传统的装饰设计模式包括四个角色:业务接口和它的实现类、装饰者和继承装饰者的具体装饰者。
其中实现类以委派的方式注入装饰者。
装饰设计模式最明显应用的就是JDK中IO相关的类,比如InputStream的子类有FilterInputStream、FileInputStream、ByteArrayInputStream,然后FilterInputStream的子类又有LineInputStream、BufferedInputStream和DataInputStream。
还有spring中对servlet的包装。
装饰设计模式和代理模式的区别在于,前者动态的增加方法而后者为了控制访问,前者将原始对象传入装饰者而后者在代理类中创建被代理对象,一个公开一个隐藏。
装饰设计模式和适配器模式的区别在于,装饰者和被装饰者常常实现相同的接口,或者装饰者是被装饰者的子类,而适配器和被适配者通常不具有相同的接口。
代理
代理是为了保护和增强目标对象,将调用者和真实被调用目标分离开。
根据目的以及实现方式可以分成很多类,常见的几种比如:
- 远程代理,为一个位于不同的地址空间的对象提供一个本地的代理对象,这个不同的地址空间可以在同一台主机中,也可以不在同一台主机中。远程代理又叫“大使”(Ambassador)。
- 虚拟代理,如果创建一个对象太耗资源,先创建一个消耗相对较小的对象来表示,真实对象只在需要时才会被真正创建。
- 保护代理,就是控制访问,可以给不同的用户提供不同级别的使用权限
- 缓存代理,为某一个操作的结果提供临时存储空间,以便于其它客户端共享数据。
- 智能引用代理,当一个对象被引用时,提供一些额外的操作,比如将对象被调用的次数记录下来等。
代理设计模式的结构和装饰设计模式很像,分为静态代理和动态代理。
在实际使用中静态代理的局限在于如果一个类中方法过多,则需要重载的方法也很多。而无论包含多少函数,动态代理只需实现一次。
在JAVA中实现动态代理有两个核心的东西,Proxy和InvocationHandler。
当然,常用的还有CGLIB方式,它的核心类为Enhancer和MethodInterceptor。
java的动态代理底层采用反射机制生成代理对象,CGLIB底层使用字节码方式生成代理类。
java动态代理被代理类需要实现接口。CGLIB则不用。
JAVA代理中Proxy静态方法生成动态代理类同样需要通过类装载器来进行装载才能使用,它与普通类的唯一区别就是其字节码是由 JVM 在运行时动态生成的而非预存在于任何一个 .class 文件中。每次生成动态代理类对象时都需要指定一个类装载器对象。
代理的典型应用就是SpringAOP了,比如AOP中封装的JdkDynamicAopProxy和CglibAopProxy。
享元
如果生成代理需要的时间和资源较多,可以使用享元模式来增加效率。并且还可以和单例模式结合使用,使用一个单例容器。
我们熟悉的String类就是享元模式的一个应用。String对象是final类型,一旦创建就不能改变,并且存储在常量池中。
Integer同样也是应用之一。
所以享元设计模式在实际开发中应用不多,因为其常用于系统底层开发,解决性能问题。可以将其理解为一个池,减小系统中大量细粒度对象的创建。
单纯的享元模式有三个角色:抽象接口、具体享元实现类、工厂。
而且,为了能做到共享,区分了内部状态和外部状态。
具体享元角色如果存在内部状态,则需要为其准备存储空间。
工厂类可以使用单例保证唯一性,它的作用是管理具体享元对象,当请求来时,由工厂向外提供享元对象,如果没有则创建一个。
而与内部状态相对的,外部状态则是客户端向工厂请求享元对象后,使用享元对象时才传入的。
为何要分为内部状态和外部状态呢?我们可以将围棋棋子使用享元模式设计,也就是有黑色棋子和白色棋子,这个就是内部状态,不变且可共享,同时,棋子在棋盘上所处的XY值就是外部状态,是需要传入的。
如果结合组合设计模式应用,则成为复合享元设计模式,也就是说具体享元对象可以变成复合具体享元对象,其中有保存单纯享元对象的容器。当然,其有add方法来添加单纯享元对象。
但是复合享元对象不能共享,由其得到的单纯享元对象可以共享。
一个复合享元对象中所有单纯享元对象元素的外部状态都是与复合享元对象的外部状态相等的;而一个复合享元对象所含有的单纯享元对象的内部状态一般是不相等的,不然就没有使用价值了。
外观(门面)
外观设计模式没有具体的类图,但一般其包括两个角色:门面和子系统。
门面负责将客户端的请求派发到对应的子系统中。也就是说对于子系统来说,门面就是另一个客户端罢了。
不过,让客户端直接调用门面而不是子系统一方面简化了客户端调用过程,另一方面也可以有效的隐藏子系统内部方法。
但是要注意,门面的功能是为子系统提供一个集中的访问管道,而不是给子系统增加新的功能。
可以结合单例设计模式使得门面对象为单例对象。
然后,可以结合抽象工厂模式让门面从工厂获取子系统实例,让子系统可以屏蔽外观类。
外观设计模式的应用例子有Tomcat中RequestFacade和ResponseFacade。因为Request中有一些方法需要和内部组件交互,所以使用public修饰,但这些方法又不希望对外公开,所以使用门面类来进行屏蔽。
桥接
桥接模式的最重要用意是将抽象和实现脱耦。脱耦的方式可以是将两者的继承关系改变为聚合关系。
这个设计模式要解决什么问题呢?比如有GIF、JPG、BMP等图片格式,然后有win、liunx、mac等操作系统,这是两个维度,可以使用桥接模式使得两个维度分离,新增图片格式或者操作系统时都不会对另一个维度造成影响。
所以,桥接模式的四个角色就有了。
- 其一是抽象类,比如所有图片的父类Image。
- 然后就是Image的子类,叫扩充抽象类,比如名为GIF的子类。
- 抽象类Image中有一个成员变量ImageShow,就是实现类接口。
- 具体实现类就是诸如Linux、windows去实现接口ImageShow。
也就是说,要更换或增加图片,只需要更换或增加Image,同理,而要更换和增加系统,只需要更换和增加ImageShow的实现类即可,符合开闭原则。
要区分桥接模式和组合模式,前者是平级之间组合,后者是整体和部分的关系。
桥接模式的经典应用为JDK中的DriverManager,其包含多个Driver。
*不变设计模式
一个对象的状态在对象被创建之后就不再变化,这就是所谓的不变模式。
在java中String就是不变模式的典型应用。
不变模式可以增强对象的强壮性,降低对象在同步访问时的开销。其根据实现分为弱不变模式和强不变模式。
要实现弱不变模式,要符合以下几个条件:
- 初始化后,没有任何方法可以改变对象的状态
- 所有属性都是私有的
- 此对象如果引用其他可变对象,需要限制外界对这些可变对象的访问,最好连这些可变对象都在不变对象的内部初始化,如果必须在外部初始化后才传入不变对象内,那最好在不变对象初始化的时候保存这些可变对象的副本。
不过,之所以叫弱不变对象是因为其子对象可能是可变的。
连子对象都不可变的成为强不变模式,强不变模式在弱不变模式的基础上还要满足以下几个条件之一:
- 类中的所有方法都是final,保证子类不能置换掉这些方法
- 类本身就是final的,这样一来它就不可能有子类。
还有,要注意“不变”和“只读”的区别,举例来说,一个人的生日是不变的,但是此人的年龄是变化的,不过年龄不能修改,是只读的。
动作(行为)型
策略
简单来说,策略模式要达到的效果是将不同的算法或行为包装到不同的策略类中,然后在上下文中可以互换和调用。
举个例子,比如购物网站结帐时,普通会员就是商品数量乘以价格,而高级会员再乘上10%的折扣,VIP乘上20%的的折扣。
一般策略类包含三个角色:上下文环境类,其包含一个策略接口引用,以及具体策略类。
策略模式可以结合工厂模式使用,工厂模式创建不同的对象,策略模式接收对象产生行为。所以注意,策略模式的的重点不在于如何实现算法,而是如何组织和调用这些算法。
策略模式在spring的Resource中应用,其有很多访问行为的实现,比如ByteArrayResource、ClassPathResource等。
模板方法
简单来说,模板方法模式的用意是准备一个抽象类,其中实现一些具体方法和构造函数,然后声明一些抽象方法来让子类实现,不同的子类可以对这些抽象方法有不同的实现逻辑。
模板方法模式中的方法分为模板方法和基本方法。
模板方法存在于抽象类中,将基本方法组合在一起形成一个总算法或一个总行为,比如先调用基本方法A,再调用基本方法B,最后调用基本方法C。
而基本方法分为三种。
- 比如A是抽象方法,使用abstract修饰,需要子类实现。
- B是具体方法,不需要子类实现。
- C是钩子方法,通常抽象类给出空实现,由子类去扩展,这也表明子类无需像接口那样实现所有方法。
这里多提一句命名规范,钩子方法应到以do打头,在HttpServlet类中,也遵从这一命名规则,如doGet()、doPost()等方法。
注意区分模板方法模式和策略模式,前者的算法骨架是固定不变的,将其中的部分逻辑交由子类完成,后者是令不同的算法可以互相替换。
同时,工厂方法模式是模板方法模式的特殊实现。
观察者(发布-订阅)
观察者模式定义了一种一对多的关系,多个观察者监听一个目标对象,如果该对象的状态发生变化了,会通知所有观察者,然后观察者更新自己的状态。
所以,观察者模式有四个角色:目标抽象类、具体目标类、观察者接口、具体观察者实现。
- 目标抽象类保存一个观察者列表,其和观察者接口为聚合关系。自然,抽象类中有对观察者的增删方法。
- 具体目标类保存当前目标类的状态。
- 具体观察者实现观察者接口,保存具体观察者的状态。
- 具体观察者的状态和具体目标类的状态有一个自洽的关系。根据情况,具体观察者中可以保存一个具体目标类的引用。
既然观察者模式又被叫做发布订阅模式,那么自然存在两种发布方式,推模式和拉模式。
java本身存在对观察者模式的支持,在java.util库里面,提供了一个Observable类以及一个Observer接口。
迭代器(游标)
用来顺序访问一个集合中的元素且不同暴露其内部表示。
迭代器模式通常存在四个角色:
- 聚集类接口,声明一个创建迭代器的方法
- 聚集类实现,实现创建迭代器的方法,返回一个迭代器实现。
- 迭代器接口,声明具体迭代器中应有的方法。
- 迭代器实现,包含了一个游标,用于记录当前访问的位置。
根据实现方式和结构的不同,迭代模式存在几个概念:
- 白箱聚集与外禀迭代子,其向外界提供宽接口。
- 黑箱聚集与内禀迭代子,其向外界提供窄接口,向迭代器提供宽接口,一般迭代器类为聚集的私有内部类。
- 静态迭代,迭代器创建时持有一份聚集对象的拷贝。
- 动态迭代,修改聚集内容会影响到迭代过程,导致Fail fast。
迭代器模式最熟悉的应用便是JDK中的Iterator。同时AbstractList.Itr迭代器是一个Fail Fast的迭代器设计。
责任链
可以设想这么一个场景,公司采购审批,项目经理可以审批1000元内的金额,部门经理可以审批1500元,总经理可以审批2000元。申请人提交申请,但不知道谁会处理这个申请,申请在项目经理、部门经理和总经理这条链上传递。
责任链设计模式一般有两个角色:处理者接口和具体处理者实现。处理者接口中可以定义对下家的设定和引用方法。
一个纯责任链模式要求具体处理者要么承担责任,要么向上传递。不允许出现承担了一部分责任后又将责任向上传递的情况。且在一个纯的责任链模式中,一个请求必须被某个处理者接收,不像不纯的责任链模式最终可以允许没有处理者处理请求。
不过在实际应用中,很少有纯的责任链模式应用。
Tomcat中的Filter就使用了责任链模式。其ApplicationFilterChain类便承担抽象处理者的角色。
命令
命令模式的意图是将发布命令和执行命令分开,委派给不同的对象,达到发送者和接收者解耦的效果。
一般命令模式存在四个角色:命令接口、具体命令实现、调用者、接收者:
- 命令接口一般声明一个execute方法。
- 具体命令实现对象中一般关联接收者并实现execute方法,在execute方法中调用接收者对象的具体操作。
- 调用者也就是请求发起者,其不需要知道具体执行者是谁,只需要运行时将具体命令对象注入并执行execute方法。
- 接收者具体执行对请求的业务处理。
有时,我们需要一次性的将多个命令组合使用,便可以实现命令队列或者叫宏命令来达到效果,具体可以增加一个类,其中初始化一个保存具体命令的集合。
命令模式还可以实现撤销与重做的功能,核心在于修改接收者,添加撤销与重做的具体方法,接收者中增加一个集合成员和一个下标成员,调用撤销与重做方法时,先判断下标合法性,然后操作集合获取对应结果。
junit中的Test接口可以看作命令模式的应用。
备忘录(快照)
备忘录模式的用意是将另一个对象的状态存储起来,在需要的时候进行还原。
一般来说,备忘录模式存在三个角色:备忘录(Memento)角色、发起人(Originator)角色、负责人(Caretaker)角色。
- 发起人可以创建备忘录对象并存储其内部状态。
- 负责人负责保存备忘录对象。
同时,备忘录模式还分为白箱实现和黑箱实现,两者主要是各角色访问接口不同。
- 白箱实现中,备忘录对象中的所有状态对所有对象公开。
- 而黑箱实现中,负责人只能调用备忘录的窄接口,将备忘录对象传给其他对象。发起人可以访问备忘录的宽接口,可以获取备忘录的所有数据,以便根据这些数据恢复发起人对象的内部状态。
而最好的实现黑箱的方式就是将备忘录设计为发起人的内部类。
同时,备忘录模式还存在多个检查点的概念,也就是可以同时存在多个备忘录,以便发起人可以恢复到备忘录模式存储的某一个检查点上。
在实际使用中,可以省略掉负责人角色,这样的备忘录模式称为自述历史模式。
备忘录模式可以和命令模式结合使用,备忘录保存记录命令历史记录。
状态
状态模式的意图是让一个对象在内部状态改变的时候关联改变行为。
一般,状态模式的结构中有三个角色:上下文、状态接口、具体状态实现。
上下文和状态接口为聚合关系,当上下文对象的特定方法被调用时,是委派给具体状态对象来执行的。也就达到了变更上下文中的状态就变更行为的效果。
在具体业务中可以想象这么一个场景,一个视频分为播放、快进、暂停、停止的状态。
状态类的切换由状态完成,为此,可以将上下文传入状态中。切换也可以由上下文完成,但如果增加状态,则需要修改上下文中方法的逻辑。
注意策略模式和状态模式的区别,应用层需要知道自己使用的策略,但不需要知道目前的状态,状态是会自动转换的。
同时状态模式和享元模式可以配合使用,在上下文中共享多个状态实例。
访问者
感觉是最复杂的设计模式,一般不需要使用它。
访问者模式包括五个角色:访问者接口、具体访问者、元素接口、具体元素、对象结构。
- 访问者接口定义对每个元素的访问行为,方法参数就是被访问的具体元素,一般来说,访问者接口中的方法数量和具体元素的数量是相同的。所以这也表示访问者模式需要元素类型稳定,不能频繁添加、移除。
- 元素接口一般定义一个accept方法,其参数为访问者接口,意思是元素接受访问者的访问。
- 而具体元素实现元素接口,在accept方法中将this传入具体访问者。
- 最后,对象结构负责管理元素集合,并可以迭代这些元素供访问者访问。
总之,访问者模式新增访问者容易,但是新增元素比较麻烦,且访问者可以访问元素的具体方法或细节,违反迪米特原则。
访问者设计模式的结构之所以这样设计,涉及到伪动态双分派这样一个概念。
一个方法所属的对象和方法的参数叫做方法的宗量。基于一个宗量的分派成为单分派,基于多个宗量的分派称为多分派。
java是一种静态多分派,动态单分派的语言。
所以,使用访问者模式就是想达到一种动态但双分派的效果,它是用两次动态单分派来实现的,所以被称为“伪动态双分派”。
也就是,在访问者模式的双分派中,要根据被访问者运行时的区别,也要根据访问者运行时的区别。在客户端中将具体访问者作为参数传递给具体元素。
Spring中的BeanDefinitionVisitor和NIO的FileVisitor是访问者模式的应用。
解释器
解释器设计模式要达到的目的是定义一个语言的文法,并且建立一个解释器来解释该语言中的句子。这里的语言指的是使用规定格式以及语法的代码。
一般,解释器模式包含4个角色:
- 表达式接口,它声明了解释方法。
- 终结符表达式,实现表达式接口,实现规则中终结符相关联的解释操作。
- 非终结符表达式,实现表达式接口,实现规则中非终结符相关联的解释操作,但非终结表达式可能包含非终结符表达式和终结符表达式,所以一般进行递归解释操作。
- 环境类,通常存储一些全局信息和需要解释的语句。环境类是否定义是可选的。
spring中的SpelExpressionParser类可解析el表达式,便是解释器模式的应用。
中介(调停)者
中介者模式的目的是降低各个相互作用对象之间的耦合程度,使得它们不用显示的互相引用,并且某一些对象之间的关系可以独立变化,不会影响其它对象之间的关系。
中介者模式一般有四个角色:
- 中介者接口,定义一到多个事件方法。
- 中介者实现,知道所有的具体同事,负责协调具体同事之间的交互。
- 抽象同事类,持有中介者接口。
- 具体同事类,不知道其它同事,和具体中介者通信。
举例来说,日常生活中使用的电脑,主板便是中介者角色,而CPU、显卡、声卡、硬盘等就是同事角色。
所以,中介者模式可以和观察者模式结合使用,使用观察者模式来进行同事角色之间的通讯。
中介者模式的缺点也很明显,如果各个同事之间的交互比较复杂,则大量的操作会集中到调停者上。