设计模式深度解析34讲学习总结

一、设计原则

1.1.为什么要学习设计模式?

趁早学习设计模式对以后进阶很有帮助,良好的设计模式掌握能力一是能帮助你顺利通过面试,找到更好的工作,二是能帮你更好地进阶架构师、技术布道者,对那些喜欢钻研技术的开发人员来说是很有益的。

1.2.七大设计原则

开闭原则

软件实体应当对扩展开放,对修改关闭

模块应该在尽量不修改代码的前提下进行拓展,这就需要使用接口和抽象类来实现预期效果。

开闭原则要求我们尽可能通过拓展来实现变化,尽可能少地改变已有模块,特别是底层模块。

开闭原则总结:

提高代码复用性

提高代码的可维护性

单一职责原则

保证设计类、接口、方法时做到功能单一,权责明确。

单一职责原则,指的是一个类或者模块有且只有一个改变的原因。

核心就是解耦和增强内聚性

单一职责可以降低类的复杂性,提高代码可读性、可维护性

里氏替换原则

所有引用基类的地方必须能透明地使用其子类的对象

里氏替换原则是开闭原则的实现基础,它告诉我们设计程序的时候尽可能使用基类进行对象的定义及引用,具体运行时再决定基类对应的具体子类型。

里氏替换原则总结:

里氏替换可以提高代码复用性,子类继承父类时自然继承到了父类的属性和方法

提高代码可拓展性,子类通过实现父类方法进行功能拓展,个性化定制

里氏替换中的继承有侵入性。继承,就必然拥有父类的属性和方法

增加了代码的耦合性。父类方法或属性的变更,需要考虑子类所引发的变更

依赖倒置原则

程序要依赖于抽象接口,不要依赖于具体实现。简单的说就是要求对抽象进行编程,不要对实现进行编程

其核心思想是:要面向接口编程,不要面向实现编程。

依赖倒置原则总结:

高层模块不应该依赖低层模块,都应该依赖抽象(接口或抽象类)

接口或抽象类不应该依赖于实现类

实现类应该依赖于接口或抽象类

接口隔离原则

Interface Segregation Principle(ISP),客户端不应该依赖它不需要的接口,类间的依赖关系应该建立在最小的接口上。

接口隔离原则总结:

接口尽量粒度化,保持接口纯洁性

接口要高内聚,即减少对外交互

迪米特法则

或者叫最少知识原则,一个软件实体应当尽可能少地与其它实体发生相互作用。迪米特法则的初衷在于降低类之间的耦合。

迪米特法则总结:

类定义时尽量内敛,少使用 public 权限修饰符,尽量使用 private、protected 等。

合成复用原则

通过将已有的对象纳入新对象中,作为新对象的成员对象来实现的,新对象可以调用已有对象的功能,从而达到复用。

原则是尽量首先使用合成 / 聚合的方式,而不是使用继承。

合成和聚合都是关联的特殊种类。合成是值的聚合(Aggregation by Value),而复合是引用的聚合(Aggregation by Reference)。

类之间有三种基本关系,分别是:关联(聚合和组合)、泛化(与继承同一概念)、依赖。

聚合,用来表示整体与部分的关系或者 “拥有” 关系。代表部分的对象可能会被代表多个整体的对象所拥有,但是并不一定会随着整体对象的销毁而销毁,部分的生命周期可能会超越整体。

合成,用来表示一种强得多的 “拥有” 关系。部分和整体的生命周期是一致的,一个合成的新的对象完全拥有对其组成部分的支配权,包括创建和泯灭。

合成复用原则总结:

新对象可以调用已有对象的功能,从而达到对象复用

二、创建型模式

2.1.简单工厂模式——别具匠心

该模式将对象的具体实例过程抽象化,并不关心具体的创建过程。通常,工厂模式被用来定义一个对象模型,之后,便可快速规模化实例化对象。

实质:一个工厂类根据传入的参数,动态决定应该创建哪一类产品类(这些产品类均继承自一个父类或接口)实例。

优点:

一个调用者想创建某个对象,只需知道其名称即可

屏蔽具体行为实现,调用者只需关心产品接口,减轻调用者负担

拓展性高,如果想增加一个产品类,只需拓展一个工厂类即可

具体分类

普通简单工厂模式:建立一个具体工厂类,对实现了同一接口的一些类进行实例的创建

多方法简单工厂

静态方法简单工厂

简单工厂的延伸 — 工厂方法模式

创建一个接口和多个工厂类

总结:

工厂方法模式中,核心的工厂类(这里为 Provider 接口)不再负责所有产品的创建,而是将具体创建的工作交给子类去做,该核心类仅扮演抽象工厂的角色,负责给出具体工厂子类必须实现的接口,而不接触哪一个产品类应该被实例化的细节,拓展性较简单工厂模式提升明显。

2.2.抽象工厂模式——分而治之

定义:为创建一组相关或相互依赖的对象提供一个接口,而且无须指定它们的具体类

工厂方法模式针对的是同一类或同等级产品,而抽象工厂模式针对的是多种类(多等级)的产品设计

产品族:一组相关或相互依赖的对象,比如,PC 机的主板和显卡就是两个相互依赖的产品线(也叫做产品族)

特点:

系统中有多个产品族,每个具体工厂负责创建同一族但属于不同产品等级(产品种类)的产品

系统一次只能消费某一族产品,即相同产品族的产品是一起被使用的

抽象工厂模式中的抽象工厂类的职责就是定义每个工厂要实现的功能,即定义多个产品族的产品的创建。这里,同一产品族下有多个产品时,对应的抽象工厂就会有多个抽象方法用来提供创建这些产品的接口。

组成角色

抽象工厂(Abstract Factory):提供了创建产品的接口,包含多个创建产品的方法,即包含多个类似 new Product () 的方法;

具体工厂(Concrete Factory):实现抽象工厂定义的接口,完成某个具体产品的创建;

抽象产品(Abstract Product):抽象产品定义,一般有多少抽象产品,抽象工厂中就包含多少个创建产品的方法;

具体产品(Concrete Product):抽象产品的实现类。

2.3.单例模式——天下无双

定义:私有化构造函数的类可以提供相应的 “接口”(一般就是静态方法)来返回自己的唯一实例供外部调用,确保只生成一个实例。

应用场景:

想确保任何情况下都绝对只有一个实例

想在程序上表现出” 只存在一个实例 “

也就是:

只有一个实例

自我实例化

提供全局访问点

提供全局访问点,就是说除了公共访问点之外,不能通过其他访问点访问该实例

一个类只能创建一个实例,那么该类就称为单例类,包含如下实现:

私有化的构造函数

私有化的类成员变量

公共的类实例的访问方法

懒汉式:类加载时没有创建实例,而是在调用 方法时才去创建单例,所以就会存在线程安全性问题。但是每次访问都有同步问题,消耗资源,影响性能

饿汉式:直接在类创建的同时就生成静态成员变量供外部使用,即预先加载法,所以不存在线程安全性问题

优缺点:

单例模式一般拓展困难,除了修改代码几乎没有选择;

单例模式与单一职责原则冲突。一个类,通常只关心它要实现的业务逻辑,但是单例模式既要关心自己是否单例,又要实现业务逻辑,融合性比较高。

应用场景:

要求生成唯一序列号的环境;

网站计数器,一般采用单例模式,否则难以同步;

文件系统、打印机、资源管理器等,因为底层资源只能同时被一方操纵,所以这些模块暴露的接口必然是单例的

Java 中的 dao、service 一般都是单例的,而 action 层一般都是多例。

Spring实现单例模式:

在 Spring 中,Bean 可以被定义为两种模式:prototype(多例)和 singleton(单例)

多例:对该 bean 每次请求时都会获取一个新的 bean 实例,类似于 new 操作。

bean 的作用域可以通过 bean 标签的 scope 属性进行设置,一般 scope 有如下几种值:

singleton(单例):任何时候获取到的 bean 都是同一个实例;

prototype(多例):任何时候获取到的 bean 都是新的实例;

request:在 WEB 应用程序中,每一个实例的作用域都为 request 范围;

session:在 WEB 应用程序中,每一个实例的作用域都为 session 范围。

Spring 的单例模式又分为饿汉模式和懒汉模式,其中饿汉模式是缺省模式,懒汉模式则需要在 bean 的定义处使用 default-lazy-init=“true” 来声明为懒汉模式

Spring 对单例的底层实现是通过单例注册表的方式实现的;Spring 为实现单例类可继承,就使用了单例注册表(登记薄)形式。

登记薄基本功能是:对于已经登记过的单例,则从工厂直接返回,对于没有登记的,则先登记,而后返回。基本点如下:

使用 Map 实现注册表

使用 protect 取代原先的 private 的构造方法,确保子类可继承

2.4.建造者模式——步步为营

定义:将一个复杂的对象的构建与它的表示相分离,使得同样的构建过程可以创建出不同的表示。所以建造者模式(Builder Pattern)也叫做生成器模式

组成角色:

建造者(Builder):Builder 角色负责定义用来生成实例的接口(API);

具体的建造者(ConcreateBuilder):ConcreateBuilder 角色是负责实现 Builder 角色定义的接口的实现类。针对不同的商业逻辑,具体化复杂对象的各部分的创建。在建造完成之后提供产品的实例;

监工(Director):Director 角色负责使用 Builder 角色的接口 API 来生成实例。内部不涉及具体产品信息,只负责保证对象各部分完整创建或按照某种顺序进行创建。即 Director 是负责指挥如何 build 的,只负责调度,具体实施交给具体的建造者;

产品(Product):即要创建的复杂对象;

使用者(Client):实际使用 Builder 模式的角色

优缺点:

封装性:客户端不必知道产品内部组合细节,只需关心我们要生成某个对象,具体对象产生细节不必知晓。Main 类并不知道 Builder 类,它只是调用了 Director 类的 construct 方法完成对象的获取;

建造者独立,易于拓展:上面我们只列举了 ConcreteBuilder 建造者类,如果需要其它建造者新建类即可。建造者之间彼此独立,系统拓展性好,符合开闭原则;

便于控制细节风险:由于具体建造者是独立的,因此可以对具体建造过程逐步细化,不会对其它模块产生影响。

应用场景:

产品类非常复杂,不同的调度产生不同的结果时,使用建造者模式比较适合;

相同的组件或配件都可以装配到一个对象,但是产生的结果又不相同,可以使用建造者模式。

建造者模式 VS 工厂方法模式

建造者模式关注的是零件类型和装配顺序(工艺)同为创建型模式,注重点不同。另外工厂模式只有一个建造方法,而建造者模式有多个建造零部件的方法并且强调建造顺序,而工厂模式没有顺序的概念。

2.5.原型模式 —— 依法炮制

定义:从设计模式的角度讲,原型模式是一种创建型模式,摆脱了类的构造模式,原型模式告诉我们,想要创建一个对象,我们不必关心对象的具体类型,而是找到一个对象,然后通过克隆来创建一个一模一样的对象。

实现关键:语言本身是否提供了 clone 方法

注意:所有的 java 类都继承自 java.lang.Object 类,而 Object 类默认提供了 clone 方法用来实现对象复制,能够实现的 java 类必须实现一个叫做 Cloneable 的接口,用来标识该类是可以被复制的,如果一个类没有实现 Cloneable 接口而调用 Object.clone 的话,那么 java 会抛出 CloneNotSupportedException 异常。

浅拷贝VS深拷贝

浅拷贝:直接将原对象 birth 属性的引用值赋给新的对象 p2 的 birth 属性,这样两个对象的 birth 指向的是用一个 Date 对象

深拷贝:将原对象 birth 指向的 Date 对象复制一份,创建一个相同的 Date 对象,然后将这个新的 Date 对象的引用赋给新拷贝的 p2 对象的 birth 属性,这样 p1 和 p2 的 birth 就分别指向了两个不同的 Date 对象

序列化和反序列化

序列化是指将对象的状态信息转换为可以存储或传输的形式的过程。在序列化期间,对象将其当前状态写入到临时或持久性存储区。以后,可以通过从存储区中读取或反序列化对象的状态,重新创建该对象。

从字节流创建对象的相反的过程称为反序列化。

应用场景

原型模式一般很少单独出现,一般都是和工厂方法模式一起搭配使用,通过 clone 来创建新的对象,然后由工厂方法返回。依赖倒置原则提醒我们创建对象的时候尽量不要依赖具体的对象类型,原型模式就很好的印证了这句话,避免僵硬地使用 new 来进行对象创建。

优点:

向客户隐藏新实例生成的细节

某些环境下,复制对象比新建对象更有效

提供让客户自主创建未知类型对象的方法

减少子类的构造,原型模式通过克隆而不是工厂方法来产生一个对象

缺点:

对象复制有时比较复杂,特别是对象层级嵌套很深时

三、结构型模式

3.1.适配器模式——承上启下

定义:将一个接口转换成客户希望的另一个接口,使接口不兼容的那些类可以一起工作,解决的痛点便是因接口不兼容导致的类不能正常工作的问题。

使用时机:

现有的类或接口不能满足需求,且一般无法直接修改现有类或接口。比方该类为三方提供,就无法修改,亦或者像A、B 这种已经塑模成型的物件,可能已大规模在使用中,所以不允许修改。

想要建立一个可以重复使用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作,这些源类不一定有一致的接口。

适配器模式,根据适配器类与适配者类的关系不同,适配器模式可分为对象适配器和类适配器两种,在对象适配器模式中,适配器与适配者之间是关联关系;在类适配器模式中,适配器与适配者之间是继承(或实现)关系。

组成角色:

目标角色(Target):该角色定义把其它类转换为何种接口,也就是我们的期望接口,可以是一个抽象类或接口,也可以是具体类。

适配器角色(Adapter):适配器可以调用另一个接口,作为一个转换器,对 Adaptee 和 Target 进行适配,适配器类是适配器模式的核心,通常都是一个具体的类。

源角色(被适配 Adaptee ):你想把谁转换成目标角色,这个“谁”就是源角色,它是已经存在的、运行良好的类或对象,经过适配器角色的包装,它会成为一个崭新、靓丽的角色。

请求者(Client):该角色负责使用 Target 定义的方法进行具体处理。

类适配器(使用继承)

class Adapter extends Adaptee implements Target

对象适配器(使用委托)

class Adapter extends Target

优缺点:

主要优点:

将目标类和适配者类解耦,通过引入一个适配器类来重用现有的适配者类,无须修改原有结构;

增加了类的透明性和复用性,将具体的业务实现过程封装在适配者类中,对于客户端类而言是透明的,而且提高了适配者的复用性,同一个适配者类可以在多个不同的系统中复用;

可以将两个互不相干的类关联在一起;

增强系统灵活性。

主要缺点:

类适配器对于 Java、C# 等不支持多重类继承的语言,一次最多只能适配一个适配者类,不能同时适配多个适配者。

应用场景:

系统需要使用一些现有的类,而这些类的接口(如方法名)不符合系统的需要,甚至没有这些类的源代码,这时创建一个适配器就能间接去改造这个类中的方法;

想创建一个可以重复使用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作。

3.2.桥接模式 —— 牵线搭桥

定义:

桥接模式 (Bridge Pattern):将抽象部分与它的实现部分分离,使它们都可以独立地变化。它是一种对象结构型模式,又称为柄体 (Handle and Body) 模式或接口 (Interface) 模式。

Bridge 就是将类的功能层次结构与实现层次结构连接起来。

类的功能层次结构 Vs 实现层次结构

功能层次结构

父类具备基本功能

子类在父类基础上添加新的功能

实现层次结构

父类声明抽象方法定义相关接口(API)

子类通过具体方法来实现接口(API)

组成角色

抽象化(Abstraction):该角色位于属于 “类的功能层次结构” 的最上层,用于定义抽象接口,一般是抽象类而不是抽象接口。其内部往往包含一个实现类接口实例(Implementor),使用委托方式进行内部调用;

改善后的抽象化,或者叫补充抽象类(RefinedAbstraction):该角色用于补充 Abstraction 功能而存在,通常情况下不再是抽象类而是具体的实现类,在内部可以直接调用 Implementor 中的业务方法;

实现者(Implementor):该角色位于 “类的实现层次结构” 的最上层,定义了用于实现 Abstraction 角色的接口(API),这里的接口并非要和 Abstraction 中定义的完全一致,Implementor 只对这些接口进行声明,具体实现还是要交给子类。通过委托,在 Abstraction 中,不仅可以调用自己方法,还可以调用到 Implementor 中定义的方法;

具体实现者(ConcreteImplementor):该角色用于实现 Implementor 角色中定义的接口,不同的实现类提供不同的业务处理方法,程序运行时,ConcreteImplementor 将替换 Abstraction 中的 Implementor,提供给抽象类具体的业务操作方法。

优缺点

抽象与实现相分离:抽象与实现相分离,从而让抽象与实现分别独立开来,分别定义接口,有助于系统分层及产生更好的结构化系统;

更好的拓展性:系统拓展时,因为抽象与实现已经分别独立,所以可以进行分别拓展不会相互影响,从而大大提高系统拓展性。

3.3.过滤器模式——挑三拣四

定义

过滤器模式(Filter Pattern)又称为标准模式(Criteria Pattern)是一种设计模式,这种模式允许开发人员使用不同的标准来过滤一组对象,通过运算逻辑以解耦的方式将它们联系起来。这种类型的设计模式属于结构型模式,说白了,就是按条件筛选一组对象出来。

目的:使用不同标准来过滤一组对象

实现:制定不同的规则来实现过滤,然后对过滤结果进行分组

组成角色

抽象过滤器角色(AbstractFilter):负责定义过滤器的实现接口,具体的实现还要具体过滤器角色去参与,客户端可以调用抽象过滤器角色中定义好的方法,将客户端的所有请求委派到具体的实现类去,从而让实现类去处理;

ConcreteFilter(具体过滤器角色):该角色负责具体筛选规则的逻辑实现,最后再返回一个过滤后的数据集合,标准的过滤器只对数据做过滤,当然也可以对集合中的数据做某项处理,再将处理后的集合返回;

Subject(被过滤的主体角色):一个软件系统中可以有一个或多个目标角色,在具体过滤器角色中会对指定感兴趣的目标进行处理,以确保后面的数据确实是我想要的。

过滤器延伸——管道和过滤器

管道将一个个过滤器连接起来,形成一个过滤器链(过滤器链可以携带多个过滤器,并且可以自定义顺序执行它们)

可以存在一个过滤器管理器的角色,负责管理过滤器和过滤器链

特点

可插拔:过滤器的设计概念要求其是支持可插拔设计的;

有序性:过滤器是被设计为一组组的过滤装置,要实现数据过滤,就必须有顺序性要求,比如我们要设计编解码过滤器,用户请求过来的 xml 数据会优先通过 xml2json 过滤器进行数据处理,完了再在响应发出前进行相应的 json2xml 过滤处理,以保证客户端交互以 xml 数据格式为准的同时系统内部数据交互还是维持 json 格式不变;

过滤器的独立性:每种过滤器必须是独立的实体,其状态不受其它过滤器的影响,每个过滤器都有自己独立的数据输入输出接口,只要各个过滤器之间传送的数据遵守共同的规约就可以相连接

3.4.组合模式——高度一致

定义

组合关系:部分与整体的关系,有了整体才有部分,部分不能脱离整体而存在

聚合关系:整体和部分的关系,部分可以单独存在

一致:整体和部分一致

3.4.装饰器模式——添砖加瓦

定义:在不改变原有功能的基础上添加额外的装饰功能

组成角色

抽象构件(Component ):Component 是一个接口或者抽象类,也是最原始的对象,属于模式核心角色。用于定义一些抽象的接口或功能,以便后面的 ConcreteComponent 和 ConcreteDecorator 角色去实现;

具体构件(ConcreteComponent):ConcreteComponent 是最原始、最基本的接口或抽象类 Component 的实现,在模式中充当被装饰的角色,也就说我们模式要装饰的对象就是 ConcreteComponent;

抽象装饰角色(Decorator):Decorator 一般是一个抽象类,实现接口或者抽象方法,其内部不一定有抽象方法定义,有可能只是单纯继承下 Component 抽象构件;但是其内部一般都有一个 Component 角色的引用,表示 Decorator 需要装饰的对象,一般该对象是 private 或者 protected 声明;

具体装饰器角色(ConcreteDecorator):具体的装饰器类,继承 Decorator 抽象装饰器角色,实现了 Component 抽象角色中定义的接口(API)

3.5.外观模式——分离解耦

定义

为各个子系统的一组接口提供一致的调用窗口或门面,使得子系统更容易使用,使得复杂的子系统与客户端分离解耦

组成角色

门面角色(Facade):门面模式自然少不了门面角色,这就是我们的 Facade 类,一般情况下,该角色会将客户端的请求委派给相应的子系统去调用,也就说该角色实际没有啥实质性的业务逻辑,只是一个单纯的委派类,用来实现客户端和子系统的解耦;

子系统角色(SubSystem):子系统并不是一个单一的类,而是众多类的一个系统集合。一般而言,子系统并不知道门面角色的存在,也就说对子系统而言,门面角色是完全透明的。子系统各自实现自己的功能,包括类之间的相互调用等,这些都不受门面角色的影响。

优缺点

外观模式优点:

实现了子系统与客户端之间关系的解耦;

客户端屏蔽了子系统组件,使得客户端所需处理的对象数目有所减少,使得子系统使用起来更加容易

外观模式缺点:

增加新的子系统可能需要修改外观类或者客户端的源代码,违背了开闭原则;

外观类并没有阻断子系统被外部使用的可能性

3.6.享元模式——共享重用

定义:

使用共享对象有效地支持大量的细粒度的对象

组成角色

抽象享元角色(Flyweight):一般是一个具体的抽象类,同时定义了对象的外部状态和内部状态的接口或实现;

具体享元角色(ConcreteFlyweight):具体的一个产品类,实现了抽象享元角色中定义的接口,该角色需要注意的是内部状态的处理应该与环境无关;

享元工厂(FlyweightFactory):该角色指责一般比较清晰,就是一个池工厂,提供池对象和获取池中对象的方法

优缺点:

优点

有效减少内存数量,使得相似或相同的对象在内存中只保留一份,有效解决内存溢出的问题

享元马氏的外部状态相对独立,而不影响其内部状态,从而使得享元对象可以在不同的环境中被共享

缺点

内部状态与外部状态的分离会变得比较复杂,使得程序更加复杂化

享元工厂中的对象进行获取的时候可能会存在线程安全问题

3.7.代理模式——托孤寄命

定义

代理模式给某对象提供一个代理对象,由代理对象来控制对原对象的引用

静态代理组成角色

抽象主题角色(Subject):抽象主题角色往往是一个抽象类或接口,用来定义被委托类也就是真实的业务处理类和代理类的一些通用操作;

具体的主题角色(RealSubject):该类实现 Subject,是真实被委托的类,也是具体业务逻辑的真正执行者;

代理类或委托类(Proxy):该类同样实现 Subject,在客户类和本地之间充当中介作用,将客户端的业务操作委托给 RealSubject 执行,并在执行前后做一些预处理或者善后工作

动态代理

jdk动态代理

通过反射技术来创建类的加载器并且创建类的实例,根据类执行方法并在方法执行前后进行前置或者后置通知等处理。使用到的就是 Proxy 类的静态方法 newProxyInstance,该方法会返回一个代理类对象

cglib动态代理

优缺点

优点:

智能化:通过动态代理可以实现将页面表单元素映射到Java对象

中介隔离:使用代理类隔离客户类和目标类;降低系统耦合度,扩展性好;可以起到对目标对象的保护及增强功能

缺点:

多出代理类,导致类设计时类数量增多,请求变缓

四、行为型模式

4.1.责任链模式——以讹传讹

定义:

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

组成角色:

请求者(Client):Client 角色就是向链发送请求的角色,在上面的例子中,Main 函数扮演这个角色;

责任人的抽象类角色(Handler):Handler 角色是模式的核心,Handler 知道下一个责任人是谁,并根据责任人的处理能力选择是否将请求转发至下一个责任人。上面例子中,由 Handler 类扮演该角色;

具体责任人对象(ConcreteHandler):该角色是具体处理请求的对象,上面例子中,由 Primary、Middle、Senior 类扮演

优缺点

优点:

责任链模式将请求和处理分开,请求者不知道时谁处理的,处理者可以不用知道请求的全貌

提高系统的灵活性

缺点:

降低系统性能:特别是链较长时,请求要从链头遍历到链尾

不利于调试:链式调用类似于递归方式,加剧调试难度

4.2.命令模式——上令下达

命令模式(Command Pattern)又称行动(Action)模式或交易(transaction)模式

英文定义:Encapsulate a request as an object, thereby letting you parameterize clients with different requests, queue or log requests, and support undoable operations.

中文定义:将发送者、接收者和调用命令封装成对象,客户端调用的时候可以选择不同的对象,从而实现发送者和接收者的完全解耦

组成角色

命令接口(Command)角色:该角色声明一个接口,定义需要执行的命令;

具体命令实现类(Concrete Command)角色:该角色定义一个接收者和行为之间的弱耦合,实现命令方法,并调用接收者的相应操作;

调用者(Invoker)角色:该角色负责调用命令对象执行请求;

接收者(Receiver)角色:该角色负责具体实施和执行请求动作(方法);

客户端(Client)角色:串连执行整个流程

优缺点

优点:

类间解耦:调用者角色与接收者角色之间没有任何依赖关系,调用者实现功能时只需要调用 Command 中的 execute() 方法即可,不需要了解是哪个接收者执行;

可扩展性:Command 的子类可以非常容易地扩展,而调用者 Invoker 和高层次的模块 Client 不产生严重的代码耦合

缺点:

使用命令模式会导致系统有过多的具体命令类,因为针对每一个命令都需要设计一个具体命令类

应用场景

系统需要支持命令的撤销(undo),命令对象可以把状态存储起来,等到客户端需要撤销时,可以调用 undo() 方法,将命令所产生的效果撤销;

系统需要支持命令的撤销(Undo)操作和恢复(Redo)操作;

系统需要将一组操作组合在一起,使用命令模式来实现,可以很方便的增加新的命令

4.3.解释器模式——解疑答惑

解释器模式(Interpreter Pattern)提供了评估语言的语法或者表达式的方式,属于一种行为型的设计模式

英文定义:Given a language, define a representation for its grammar along with an interpreter that uses the representation to interpret sentences in the language.

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

终结符号和非终结符号

终结符号:运算参数一般就是英文字母,执行时各个参数需要赋上具体的数字值去替代英文字母执行,运算参数有一个共同点就是不管是 a、b 或者其它参数,除了被赋值之外不需要做其它任何处理,是执行时的最小单元

非终结符号:运算符号是进行运算时具体要被解释器解释执行的部分,想象一下,假如我们计算机不知道如何处理类似 +、- 这种符号,我们是不要针对每一个符号写一个解释方法,以便告诉计算机该符号需要进行何种操作,这也就是解释器模式的核心——需要完成逻辑的解释执行操作,而运算符号就是非终结符号

组成角色

抽象解释器(AbstractExpression):抽象解释器是一个上层抽象类,用来抽取定义公共的解释方法:interpreter,具体的解释任务交给子类去完成;

终结符表达式(TerminalExpression):是抽象解释器的子类,实现了与文法中的元素相关的解释操作。一般模式中只会有一个终结符表达式也就是终结符的类,但是会有多个实例,比如:a、b、c,这些终结符号可以任意多种但是只有一个类来描述;

非终结符表达式(NonTerminalExpression):也是抽象解释器的子类,用来实现文法中与终结符相关的操作。该角色一般会有多个实现类,比如 +、- 运算符号就各自对应一种类实现,分别对应加法解释类和减法解释类,非终结符表达式的类的个数一般会有很多,因为我们可执行的操作一般会有很多,这也从侧面加剧了该模式下类设计的复杂性;

上下文(Context):上下文一般用来定义各个解释器需要的数据或公共功能,比如上面的表达式,我们使用上下文来保存各个参数的值,一般是一个 HashMap 对象,以便后面所有解释器都可以使用该上下文来获取参数值

优缺点

优点:

拓展性强:修改文法规则只需要修改相应的非终结符表达式就可以了,即增加非终结符类就可以了

缺点:

采用递归调用方法,不利于调试,增加了系统的复杂性以及降低了系统执行的效率;

解释器模式比较容易造成类设计的膨胀,主要是非终结符表达式类会随着系统的复杂性而膨胀;

可利用的场景比较少;

对于比较复杂的文法不好解析

应用场景

一个简单语法需要解释的场景,如:sql语法分析,用来解析那种比较标准的字符集;

重复发生的问题可以使用解释器模式,如:日志分析,日志分析时基础数据是相同的类似于我们的终结符,但是日志格式往往是各异的,类似于非终结符,只需要指定具体的实现类即可

4.4.迭代器模式——循环迭代

迭代器模式(Iterator Pattern)又称为游标(Cursor)模式

英文定义:Provide a way to access the elements of an aggregate object sequentially without exposing its underlying representation.

中文定义:提供一种方法访问一个容器对象中各个元素,而又不需暴露该对象的内部细节

注意:迭代器是为容器服务的,容器是指用来容纳其他对象的对象,例如,Collection 集合类型、Set 类等

组成角色:

抽象迭代器(Iterator)角色:该角色负责定义访问和遍历元素的接口;

具体迭代器(Concrete Iterator)角色:该角色实现 Iterator 接口,完成容器元素的遍历;

抽象聚集(Aggregate)角色:该角色提供创建迭代器角色的接口;

具体聚集(Concrete Aggregate)角色:该角色实现抽象聚集接口,创建出容纳迭代器的对象

优缺点

优点:

迭代器模式将数据存储和数据遍历的职责进行分离;

迭代器模式简化了遍历容器元素的操作;

迭代器模式使不同的容器,具备一个统一的遍历接口;

迭代器模式封装了遍历算法,使算法独立于聚集角色,调用者无须知道聚集对象的类型,即使聚集对象的类型发生变化,也不会影响遍历过程

缺点:

由于迭代器模式将数据存储和数据遍历的职责进行分离,如果增加新的聚合类,同时需要增加与之相对应的迭代器类,这使得类的个数会成对增加,在某种程度上来说增加了系统的复杂性

4.5.中介者模式——牵线搭桥

定义:定义一个中介对象来封装对象之间的交互,使原有对象之间耦合松散,并且可以独立地改变它们之间的交互

组成角色:

中介者(又称仲裁者,Mediator):负责定义与 Colleague 角色进行通信和做出角色的接口;

具体中介者、仲裁者(ConcreteMediator):负责实现 Mediator 角色定义的接口,负责具体的业务执行;

同事角色(Colleague):负责定义与 Mediator 角色进行通信的接口;

具体同事角色(ConcreteColleague):实现 Colleague 角色定义的接口,一般会有多个实现类

优缺点

优点

弱化对象间的依赖关系,即松耦合,降低同时类的耦合度,符合迪米特法则

将对象间的调用关系进行封装,使得对象更容易复用

缺点

如果对象增多,就要去修改抽象中介者和具体的中介者角色

中介者角色承担了太多了业务逻辑功能,流程复杂时就会显得比较臃肿,不好管理

4.6.观察者模式——发布订阅

观察者模式(Observer Pattern)也称发布订阅模式

英文定义:Define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.

中文定义:定义对象间一种一对多的依赖关系,使得每当一个对象改变状态,则所有依赖于它的对象都会得到通知并被自动更新

组成角色:

抽象主题(Subject)角色:该角色又称为 “发布者” 或” 被观察者 “,可以增加和删除观察者对象;

具体主题(Concrete Subject)角色:该角色又称为 “具体发布者” 或 “具体被观察者”,它将有关状态存入具体观察者对象,在具体主题的内部状态改变时,给所有登记过(关联了观察关系)的观察者发出通知;

抽象观察者(Observer)角色:该角色又称为 “订阅者”,定义一个接收通知的接口,在得到主题的通知时更新自己;

具体观察者(Concrete Observer)角色:该角色又称为 “具体订阅者”,它会实现一个接收通知的方法,用来使自身的状态与主题的状态相协调

优缺点

优点:

观察者和被观察者之间,实现了抽象耦合。被观察者角色所知道的只是一个具体观察者集合,每一个具体观察者都符合一个抽象观察者的接口。被观察者并不认识任何一个具体的观察者,它只知道它们都有一个共同的接口。由于被观察者和观察者没有紧密的耦合在一起,因此它们可以属于不同的抽象化层次,且都非常容易扩展;

此模式为广播模式,所有的观察者只需要订阅相应的主题,就能收到此主题下的所有广播

缺点:

观察者只知道被观察者会发生变化,但不知道何时会发生变化;

如果主题之间有循环依赖,会导致系统崩溃,所以在使用时要特别注意此种情况;

如果有很多个观察者,则每个通知会比较耗时

应用场景

关联行为的场景,例如,在一个系统中,如果用户完善了个人资料,就会增加积分、添加日志、开放一些功能权限等,就比较适合用观察者模式;

消息队列,例如,需要隔离发布者和订阅者,需要处理一对多关系的时候

4.7.状态模式——虚怀若谷

英文定义:Allow an object to alter its behavior when its internal state changes.The object will appear to change its class.

中文定义:允许一个对象在其内部状态改变时改变其行为,这个对象看起来好像是改变了其类。状态模式是一种对象行为型模式

组成角色

上下文角色(Context):上下文角色一般是一个类,上下文角色会聚合很多和 state,这些 state 使用静态常量修饰,并且负责 state 的状态切换;另外上下文角色还会包含抽象状态角色中定义的所有行为如 request,然后内部将请求委托给 state 的 handle 处理;

抽象状态角色(State):抽象状态角色一般是一个抽象类,用来定义具体状态的公共行为比如 handle,任何具体状态都必须实现该抽象类中的抽象方法;

具体状态角色(ConcreteState):继承抽象状态角色,实现抽象方法,实际处理来自 Context 的委托请求,当 Context 改变状态时行为也跟着改变

优缺点:

减少代码体积,利于拓展:状态模式可以消除繁杂的条件判断语句块,使得业务逻辑清晰,很好地应对对象状态的增加、删除的业务场景,因为添加新的状态只需要增加新的状态类就好了;

状态模式状态很多时会导致状态类比较多,子类太多的时候就不方便维护管理了

应用场景:

行为随状态改变而改变的场景;

化繁为简,如果代码中包含大量的条件语句块比如 switch…case、if 等,这些语句块的出现会导致业务逻辑变更时代码块也会变更,对状态的增加、删除时的调整修改起来比较吃力时就可以考虑状态模式

4.8.空对象模式——包揽验证

在空对象模式(Null Object Pattern)中,一个空对象取代 NULL 对象实例的检查

英文定义:Provide an object as a surrogate for the lack of an object of a given type. The Null Object provides intelligent do nothing behavior, hiding the details from its collaborators.

中文定义:为缺少的对象提供一个默认的无意义对象,用来避免 Null 对象的产生。就是用一个空对象,来取代程序中的 Null 值判断,从而让调用者可以直接使用对象,而无需关心对象是否为 Null

组成角色:

抽象对象(Abstract Object)角色:声明统一的对象行为(属性和方法);

具体对象(Concrete Object)角色:确实存在的具体对象,程序中的非 Null 对象;

空对象(Null Object)角色:非具体存在的对象,Null 对象;

对象工厂(Object Factory)角色:根据传递的标识得到相关类的工厂类,返回值可以是具体对象或 Null 对象

优缺点:

优点

省去代码中对 Null 值的判断和检查;

让代码显的更加优雅和可读性更高;

让系统更加稳定,避免程序抛出 NullPointerException 异常

缺点

因为增加了更多的类信息,从而使系统更复杂

应用场景:JDK8中的Optional 对象使用的就是空对象模式,避免空指针的异常。该类中有以下几个重要的方法

ofNullable () 方法:为指定的值创建一个 Optional, 如果指定的值为 null,则返回一个空的 Optional 对象;

orElse () 方法:如果有值则将其返回,否则返回指定的其它值;

map () 方法:如果创建的 Optional 中的值存在,对该值执行提供的 Function 函数调用;

flagMap () 方法:如果创建的 Optional 中的值存在,就对该值执行提供的 Function 函数调用,返回一个 Optional 类型的值,否则就返回一个空的 Optional 对象

4.9.策略模式——随机应变

英文定义:Strategy Pattern:Define a family of algorithms,encapsulate each one,and make them interchangeable.

中文定义:定义一组算法,然后将这些算法封装起来,以便它们之间可以互换,属于一种对象行为型模式

组成角色

上下文角色(Context):该角色一般是一个实现类或者封装类,起到一定的封装及隔离作用,实际接受请求并将请求委托给实际的算法实现类处理,避免外界对底层策略的直接访问;

抽象策略角色(Strategy):该角色一般是一个抽象角色,为接口或者抽象类扮演,定义具体策略角色的公共接口;

具体策略角色(ConcreteStrategy):实现抽象策略角色的接口,为策略的具体实现类

优缺点

优点

所有策略放入一组抽象策略接口中,方便统一管理与实现

缺点

策略模式每种策略都是单独类,策略很多时策略实现类也很可观;

客户端初始化 Context 的时候需要指定策略类,这样就要求客户端要熟悉各个策略,对调用方要求较高

应用场景

需要自由切换算法的场景

需要屏蔽算法实现细节的场景

4.10.模板模式——复制创新

模板模式(Template Pattern)又被称作模板方法模式(Template Method Pattern)

英文定义:Define the skeleton of an algorithm in an operation, deferring some steps to subclasses. Template Method lets subclasses redefine certain steps of an algorithm without changing the algorithm’s structure.

中文定义:定义一个操作中的算法的框架,而将一些步骤延迟到子类中。使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。简单来说,就是为子类设计一个模板,以便在子类中可以复用这些方法。

组成角色:

抽象模板(Abstract Template)角色:该角色定义一个或多个抽象操作,以便让子类实现;这些抽象操作是基本操作,是一个顶级逻辑的组成步骤,该角色还需要定义一个或几个模板方法

具体模板(Concrete Template)角色:该角色实现抽象模板中定义的一个或多个抽象方法,每一个抽象模板角色都可以有任意多个具体模板角色与之对应,而每一个具体模板角色都可以给出这些抽象方法的不同实现,从而使得顶级逻辑的实现各不相同。

模板模式中,方法分为两类:

模板方法

基本方法:

抽象方法:一个抽象方法由抽象类声明,由具体子类实现。在 Java 语言里抽象方法以 abstract 关键字声明;

具体方法:一个具体方法由抽象类声明并实现,而子类并不能修改或重写,此方法通常会被声明为 final;

钩子方法:在抽象类中预留一个 “钩子”,也就是实现一个空方法,作为方法的默认实现,子类可以选择重写(重新构建)或者不重写

优缺点

优点

提高了代码的复用性,将相同部分的代码放在抽象的父类中;

提高了拓展性:将不同的代码放入不同的子类中,通过对子类的扩展增加新的行为;

符合开闭原则:行为由父类控制,通过子类扩展新的行为

缺点

每个不同的行为都要新增一个子类来完成,抽象类中的抽象方法越多,子类增加成本就越高。而且新增的子类越多,系统就越复杂

应用场景

多个子类有公共方法,并且逻辑基本相同时;

可以把重要的、复杂的、核心算法设计为模板方法,其他的相关细节功能则由各个子类实现;

重构时,模板方法模式是一个经常使用的模式,把相同的代码抽取到父类中,然后通过钩子函数约束其行为

4.11.访问者模式——另寻他主

英文定义:Represent an operation to be performed on the elements of an object structure. Visitor lets you define a new operation without changing the classes of the elements on which it operates.

中文定义:封装一些作用于某种数据结构中的各元素的操作,它可以在不改变数据结构的前提下定义作用于这些元素的新的操作。简单地来说,就是将数据结构和数据操作相分离

组成角色:

访问者(Visitor):定义对不同的元素进行访问时的抽象行为,一般来说,有多少个具体元素,就有多少个抽象接口;

具体访问者(ConcreteVisitor):实现上面 Visitor 定义的所有接口,用来指定该访问者对各个元素进行访问时的具体行为,在本文中由 Root 用户和普通用户扮演该角色;

元素(Element):抽象的被访问的元素,一般会定义一个 accept 方法,指定其被访问时的抽象行为;

具体元素(ConcreteElement):具体的被访问的元素,实现上面 Element 的 accept 方法,各个元素负责定义自己的 accept 行为,来表示其被访问时的行为,本文中由 FileElement 和 DictionaryElement 类扮演;

对象结构(ObjectStructure):对象结构实际上是一个被访问元素的集合,好比一个元素容器,对容器的具体元素的访问表现出的行为如何,这是由访问者模式决定的

优缺点

优点

数据结构和数据操作相分离;

对访问者拓展性良好,只需要增加新的访问者类即可;

各个角色职责明确,符合单一职责原则

缺点

元素变更时会导致整个代码都要调整

应用场景:

对象的结构(元素)比较稳定,而访问者频繁变动的场景;

数据操作和数据结构分离的场景

五、J2EE模式

5.1.MVC模式——合作共赢

MVC 全称是 Model-View-Controller(模型 - 视图 - 控制器) ,是一种软件设计典范,用一种业务逻辑、数据、界面进行分离的开发模式

组成角色:

Model(模型):用于存储和操作数据(库)的类,可以是存储容器,也可以带有逻辑,用于返回结果给控制器;

View(视图):展示给用户的界面,用于展示和操作;

Controller(控制器):用于连接模型层和视图层的中间控制器

优缺点

优点

分工明确:有利于团队开发分工协作和质量控制,降低开发成本;

耦合性低:模块间相互比较独立,比如,在业务流程和交互方式不变的情况下,可以任意调整前端页面的样式,而不影响其他模块;

重用性高:以模型层来说,可以提供给多个控制器来使用

缺点

增加了系统实现的复杂性,对于简单的应用并不适用;

减低了系统的运行性能,因为程序的执行经过的层数过多,所以会带来一定的性能损耗

应用场景

Web 开发领域:由于 Web 开发的特殊性,采用 MVC 进行结构划分,一方面会提升团队的协同开发效率,专人干专事,另一方面也有利于日后软件产品的维护和升级,同时能够提升软件模块的复用性;

移动互联开发:目前移动互联开发,比如:Android、iOS 开发也在采用 MVC 框架,对于这种展示层修改频率比较高的应用,采用 MVC 的方式,使得修改展示层更加的高效;

实用型工具类程序:与前两种开发不同,实用型工具类程序,更多修改的是展示层之外的代码,这种在不更改用户操作习惯的方式下,静默地调整和升级代码的方式,也比较适合用 MVC 设计模式

5.2.业务代表模式——各司其职

定义:

业务代表模式(Business Delegate Pattern)主要是为了实现表现层和业务层的解耦,使用业务代表模式可以支持各种场景

组成角色

客户端(Client):表现层,可以是 JSP、Servlet 或者 Java 代码。上面的例子中指的就是去银行办理业务的客户;

业务代表(Business Delegate):和客户端打交道的实体类,提供了对业务服务方法的访问。对应的是业务顾问;

查询服务(Look Up Service):用来查询具体业务范畴或对象的具体业务实现,对应上文的就是业务顾问要根据查询服务查询对客户进行服务的具体窗口(服务实现);

业务服务(Business Service):具体的业务服务实现类,对应的就是具体的服务窗口

优缺点

表现层和业务层分离,利于应用解耦;

客户端可以根据需要自主选择业务代表,符合面向对象的特点

应用场景

业务的表现层和业务层解耦:传统的 MVC 架构中,为了实现 JSP、Servlet 等与业务层解耦,可以提高系统的开发效率以及模块的复用性

5.3.传输对象模式——对象传递

定义:

传输对象模式(Transfer Object Pattern)是指客户端到服务器一次性传递具有多个属性的数据,以避免多次调用远程服务器

组成角色

传输对象(Transfer Object):简单的实体类,只有 getter/setter 方法;

业务对象(Business Object):为客户端提供业务数据,用于数据处理和构建传输对象;

客户端(Client):发送请求或者发送传输对象到业务对象

优缺点

优点

减少了远程调用次数:通过在单个远程调用中传输更多的数据,应用程序可以减少远程调用次数;

提高了性能:远程调用可以使应用程序的运行速度大大降低,减少调用次数是提高性能的最佳方法之一;

简化了程序代码:使用传输对象的模式使代码可读性更高,让程序看起来更简单

缺点

增加了复杂性:由于要兼容多个版本的程序,可能需要创建更多的实体类来适用各个版本的程序,这会让程序变得更加复杂

应用场景

希望减少程序中远程调用次数;

希望提高程序获取资源的性能;

程序需要传输多个数据时

5.4.数据访问对象模式——去伪存真

定义:数据访问对象模式(Data Access Object Pattern)又称为 DAO 模式,是一种面向对象的数据访问接口,DAO 一般都是和数据库打交道,属于业务逻辑和数据库中间的环节,负责业务逻辑数据的持久化

组成角色:

数据访问对象接口(Data Access Object Interface):提供数据持久化或数据访问的抽象接口定义;

数据访问对象具体实现类(Data Access Object Concrete Class):负责实现数据访问对象接口,真正对数据进行操作的实现类,底层数据源可以是数据库、内存、Xml、文件数据等等;

模型对象或值对象(Model Object/Value Object):传统的 POJO(Plain Ordinary Java Object),可以理解为简单的实体类

优缺点

优点

业务层和数据持久层分离,减轻系统耦合度;

数据访问对象单独抽离出来,可以适配各种底层持久化类型,提高系统的拓展性

缺点

每添加一个实体类,就必须添加一套 DAO 接口和一套 DAO 实现类,会导致代码重复臃肿

应用场景

DAO 的引入,帮助我们实现对持久化层的操作,只关心业务逻辑;

一切和持久化层打交道的应用场景都会感受到数据访问对象模式的影子,有数据访问就会有 DAO 的存在

5.5.前端控制器模式——集中控制

定义:前端控制器模式(Front Controller Pattern),是提供一种可以集中式管理请求的控制器,控制器把所有的用户请求进行统一的管理和分配

组成角色:

前端控制器(Front Controller):提供应用程序的统一请求入口,这个应用程序可以是 Web 程序,也可以是基于桌面的应用程序;

调度器(Dispatcher):它的作用是把请求调度到相应的处理程序,前端控制器可以调用一个或多个调度器来完成请求分发;

视图(View):用来展示和响应用户的界面

优缺点

优点

集中控制:前端控制器为所有请求提供了统一入口,这样就完成了集中请求控制;

提高了代码的复用率:不用在每个页面单独进行权限验证或日志记录,而是提供了统一的处理代码,提高了代码的复用率;

维护成本更低:因为提供了代码的统一处理,所以需要改动的代码就更少,对应的维护成本也更低;

职责分工明确:因为提供了统一的权限验证和日志记录等,所以之后的页面可以更专注地处理自己本身的业务,角色之间的分工也更明确

缺点

增加了系统复杂度:因为不是直接触到,所以增加了系统的复杂性,对新手来说更是如此;

降低了程序运行速度:因为提供了统一的权限判断,所以相比于直接请求的方式,统一的拦截和调度会降低程序的运行速度

应用场景

统一的权限处理

统一的日志记录

统一的逻辑处理

5.6.拦截过滤器模式——法无二门

定义:拦截过滤器模式(Intercepting Filter Pattern)是一种普遍应用的模式,该模式提供对应用程序请求前后进行一些预处理拦截,比如日志打印、权限拦截等,Web 开发中使用非常普遍

组成角色:

过滤器(Filter):负责对请求进行过滤处理的实际类,在请求前后执行一些逻辑调用;

过滤器链(Filter Chain):过滤器组成的集合,另外该角色还包括一个被过滤的目标对象,指定该对象被哪些过滤器拦截;

目标对象(Target):实际被过滤或者请求调用的业务模块,是客户端实际需要请求的资源载体;

过滤管理器(Filter Manager):用来管理过滤器链的实际类,包括对过滤器链中过滤器的管理和目标对象的管理;

客户端(Client):实际向目标对象发出请求的对象

优缺点

优点

对目标对象的请求优先经过过滤器处理,可以有效保护目标资源,比如一些权限过滤器、编解码过滤器、黑白名单拦截等;

拦截过滤器模式让我们可以流式处理各种请求,有利于模块解耦,支持可插拔设计

缺点

过滤器太多会导致出现问题不好定位,另外过多的过滤器也会加剧请求时长

应用场景

执行目标资源请求时的一些预处理工作,比如编码设置、数据解密,一般这类过滤器会选择对请求直接放行;

执行应用的安全控制、日志记录等工作,可以使用过滤器进行,当然日志记录也可以使用切面配合自定义注解实现;

对请求的一些后置处理,比如返回格式转换、响应时长记录等。这些过滤器一般都是在请求结束后进行的

5.7.服务定位器模式——缓存增速

定义:

服务定位模式(Service Locator Pattern)是为服务调用者提供一个注册中心(统一调用入口),把具体相关的服务注册到服务中心,这个注册中心被称为服务定位器。在首次请求某个服务时,服务定位器会查找服务,没有已经存在的服务,会新建并缓存该服务对象。当再次请求相同的服务时,服务定位器会在它的缓存中查找,这样可以在很大程度上提高应用程序的性能。

组成角色:

服务(Service):实际处理请求的服务(类);

上下文对象(Context):为服务定位器提供要获取的服务类(Service)。

服务定位器(Service Locator):获取服务的统一入口,为客户端提供缓存或非缓存服务类。

缓存(Cache):缓存服务对象,以便下次调用时直接复用;

客户端(Client):服务调用者,通过服务定位器调用服务对象

优缺点

优点

解耦服务提供者和服务调用者;

更好地扩展和升级,例如,当更换服务提供者算法时,客户端调用者是无感知的;

使用缓存类提高了程序执行性能

缺点

增加了出现问题的几率:由于提供了统一的定位器,而隐藏了类的依赖关系,让本来可以在编译期暴露的问题,在运行时才能被发现;

增加了排查问题的难度:服务调用者无法确切地知道服务提供者的真实情况,如果遇到问题很难排除

应用场景

根据 JNDI(Java Naming and Directory Interface,Java 命名与目录接口)查询定位各种服务

你可能感兴趣的:(设计模式深度解析34讲学习总结)