大数据架构设计

图文并茂走进《结构型模式》,原来这么简单

2021-01-13  本文已影响0人  一线开发者

上篇介绍完几种创建型模式,不知道看完的小伙伴创建对象的方式有没有变得更溜了。如果没有的话,那今天更得好好学一学 结构型模式,知己知彼百战百胜嘛,清楚对象是什么结构才能更好的创建出来!

我们老样子开场,先清楚一下今天要学习的内容:

图文并茂走进《结构型模式》,原来这么简单

设计模式

一、结构型模式

结构型模式 描述如何将类后对象按某种布局组成更大的结构,它分为 类结构型模式 和 对象结构型模式。前者采用继承机制来组织接口和类,后者采用组合或聚合来组合对象。

由于组合关系或聚合关系比继承关系耦合度低,满足"合成复用原则" ,所以 对象结构型模式类结构型模式 具有更大的灵活性。

我们在上面已经了解都有 7结构性模式 ,我们接下来就先来认识第一种设计模式 代理模式

1)代理模式

由于某些原因需要给某对象提供一个代理以控制对该对象的访问,这时,访问对象不适合或者不能直接引用目标对象,代理对象作为访问对象和目标对象之间的中介

代理模式 作为开发的我们肯定也不会感到陌生,JDK 代理、CGLib 代理 随口就来。在 Java 中的代理按照代理类生成实际不同又分为 静态代理 和 动态代理。

其中动态代理又分为 JDK 动态代理 和 CGLib 代理 两种

1. 静态代理

我们结合 静态代理模式UML图 来了解下:

图文并茂走进《结构型模式》,原来这么简单

可以看出 静态代理模式 有三种角色,分别是:

我们举个例子理解一下:比如之前我们如果需要乘坐火车的话,我们需要去火车站买票,但是火车站距离远我们还得坐车去火车站,万一人多还得排队,这显然是十分不方便的。这个时候就出现了 火车站代售点,通过 代售点 我们就可以直接买票。

码示如下:

图文并茂走进《结构型模式》,原来这么简单

从上面代码中可以看出测试类直接访问的是 ProxyStation ,它作为访问对象和目标对象的中介,同时也可以对抽象方法进行增加(收取服务费)

2. 动态代理

(1)JDK 动态代理

JDK动态代理 中,抽象主题 Subject真实主题 RealSubject 是不变的,我们需要改的地方是 代理 Proxy,将静态转换成动态,然后客户端的调用方式有所改变,具体代码如下:

图文并茂走进《结构型模式》,原来这么简单

我们在代码中看到 ProxyStation 好像不需要实现 SellTicket 这个接口了,但是真的是这样吗。有兴趣的同学可以编译查看下代理类的结构,我这里接直接说结论了:

ProxyStation 在这里面其实不算是 代理模式中所说的代理类,通过 Proxy 创建出来的才是实际上的代理类,它实现了 SellTicket 这个接口,为我们提供了匿名内部类对象传递给了父类,然后在测试类通过代理对象调用的 sell() 方法,实际上是根据多态的特性,执行的是 Proxy 类 中的 sell()方法,因此会出现 代售点收取代理费 的输出。

那么如果我们没有定义 SellTicket 这个接口,只定义了 RailwayStation 这个类,那么 JDK动态代理 是不适用的,因为它必须要求定义接口,对接口进行代理。那我们这个时候就需要使用 CGLib 动态代理 了。

(2) CGLib 动态代理

由于 CGLib 是第三方工具包,我们需要先引入依赖:

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.3.0</version>
</dependency>

然后代码做出以下修改:

图文并茂走进《结构型模式》,原来这么简单

CGLib动态代理实际上是利用 ASM开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来实现代理。接下来我们比较以下各个代理:

静态代理 和 动态代理:

如果接口方法的数量比较多的话,静态代理需要对每一个方法进行中转,而动态代理是将声明的所有方法都转移到调用处理器一个集中的方法中进行处理。

如果接口增加一个方法,静态代理模式除了所有实现类需要实现这个方法外,所有代理类也都需要实现这个方法,而动态代理则不用。

结论: 动态代理相对静态代理来说更加灵活

JDK动态代理 和 CGLib动态代理:

JDK 1.6 之前,使用 CGLib 动态代理 效率会比 JDK 动态代理 要高,但是在后面的版本对 JDK 动态代理进行优化之后,在调用次数较少的情况下,JDK代理效率高于CGLib代理效率,只有当进行大量调用的时候,JDK1.6JDK1.7比CGLib代理效率低一点,但是到JDK1.8的时候,JDK代理效率高于CGLib代理。

结论:CGLib动态代理不能对声明为final的类或者方法进行代理,JDK动态代理 不能对没有接口的类进行代理。

2)适配器模式

将一个类的接口转换为客户希望的另一个接口,使得原本由于接口不兼容而不能一起工作的那些类能一起工作

适配器模式 讲究的便是 适配 两个字,我们生活中的 手机充电器(电压转换)读卡器 等就是使用到了适配器模式

图文并茂走进《结构型模式》,原来这么简单

适配器模式 又分为 类适配器模式 和 对象适配器 模式,前者的耦合度会比后者高,因此应用相对较少。

我们总结一下 适配器模式 中的几种角色:

1. 类适配器模式

图文并茂走进《结构型模式》,原来这么简单

看图我们就可以知道了个大概,首先就是定义一个适配器类来实现当前系统的业务接口,同时又继承现有组件库中已经存在的组件

我们举个例子来说明一下:

现有一台电脑只能读取 SD 卡,但是我们现在手上只有 TF 卡,如果需要读取 TF 卡就需要使用到适配器模式,创建一个读卡器(适配器)来将 TF 卡中的内容读取出来。

我们按照上面的 UML图 仿写一下:

图文并茂走进《结构型模式》,原来这么简单

大致没有什么差别,只不过多了两个实现类,然后我们接下来就可以写代码了:

图文并茂走进《结构型模式》,原来这么简单

照代码的功能来看,类适配器 确实实现了我们想要的适配功能,但是从原则上看,类适配器 却违背了 合成复用原则 ,这样会导致的问题就是只有当客户端有一个接口规范的情况下可用,否则不可用。

啥?你还不知道 合成复用原则 是啥,赶紧来复习下吧!软件设计原则。

2. 对象适配器模式

对象适配器模式可采用将现有组件库中已经实现的组件引入适配器中,该类同时实现当前系统的业务接口

怎么个意思呢,其实就是将继承关系 改成了聚合关系,UML示图如下:

图文并茂走进《结构型模式》,原来这么简单

然后修改部分代码如下:

图文并茂走进《结构型模式》,原来这么简单

3)装饰者模式

在不改变现有对象结构的情况下,动态地给该对象增加一些职责(增加额外功能)的模式

图文并茂走进《结构型模式》,原来这么简单

我们老样子看图理一下 装饰者模式 中有几种角色:

我们以 炒面 为例,炒面我们可以加 或者加 ,当然,加这两种材料的价钱肯定是不一样的,我们先画UML 来理解一下:

图文并茂走进《结构型模式》,原来这么简单

然后我们用代码实现一下:

图文并茂走进《结构型模式》,原来这么简单

功能已经完美的实现,那我们从中能发现什么好处呢?

有些小伙伴也可以已经发现了 代理模式装饰者模式 有点相似,都是可以通过 聚合 的方式动态的增加额外的责任。下面是 静态代理模式装饰者模式 的比较:

相同点

不同点

4)桥接模式

将抽象与实现分离,使它们可以独立变化。它是用组合关系代替继承关系来实现,从而降低了抽象和实现这两个可变维度的耦合度

如果有个需求我们需要创建不同的图形(矩形,圆形,正方形),每个图形需要有不同的颜色(红色,白色,黑色)。当我们不做过多思考,写出第一种解决方案应该是这样的:

图文并茂走进《结构型模式》,原来这么简单

这种应该是最简单的实现方式了,但是如果我们需要在增加一种形状或颜色,那么就需要创建爱你更多的类,这跟 工厂方法 模式有点类似,造成的结果就是 类爆炸,那么有没有优化的方案,我们就想到了第二种实现方式如下:

图文并茂走进《结构型模式》,原来这么简单

这个方案是根据实际需要对形状和颜色进行组合,对于有两个变化维度(即两个变化的原因)的系统,采用第二个方案进行设计系统,类的数量会更少,系统扩展也会更加方便。而这种模式便是 桥接模式,好处便是降低了类与类之间的耦合度,减少了代码编写量。

接下来我们看下 桥接模式UML图:

图文并茂走进《结构型模式》,原来这么简单

然后我们再来分析一下其中有哪几种角色:

接下来我们举个例子来加深一下理解吧!

现在有两种规格的画笔,分别是 大画笔小画笔 ,而且我们需要能够绘制三种颜色(红黄蓝)的颜色。如果使用传统蜡笔来实现的话,那么需要 2*3=6 支画笔,如果我们将画笔改成颜料笔,那我们只需要 3盒颜料两支画笔

UML 图 如下:

图文并茂走进《结构型模式》,原来这么简单

代码如下:

图文并茂走进《结构型模式》,原来这么简单

理解完上述的例子,我们也来分析一下 桥接模式 的优缺点

优点:

缺点:

5)外观模式

外观模式又称为 门面模式,是一种通过为多个复杂的子系统提供一个一致的接口,而使这些子系统更加容易被访问的模式,该模式对外有一个统一的接口,外部应用程序不用关心内部子系统的具体的细节,这样会大大降低应用程序的复杂度,提高了程序的可维护性

其实生活中最简单的例子便是 股票和基金 的关。有些人不会炒股,这个时候基金就是一个好的帮手,它将投资者分散的资金集中起来,交由专业的经理人进行管理,投资于股票、债券、外汇等领域,而基金投资的收益归持有者所有,管理机构收取一定比例的托管管理费用。

外观模式是迪米特法则的典型应用

没用 外观模式 之前:

图文并茂走进《结构型模式》,原来这么简单

用了 外观模式 之后:

图文并茂走进《结构型模式》,原来这么简单

如果理解了上面一层的关系,那么得出 UML图 也是十分容易:

图文并茂走进《结构型模式》,原来这么简单

外观模式 中存在以下几种角色:

那我们就举个例子简单说明一下吧!

小米的生态现在都挺好用的,只需要我们对 小爱同学音箱(Facade) 进行语音控制便可以控制智能家居(灯,空调,窗帘 --- SubSystem) ,这里面其实就是用到 外观模式

图文并茂走进《结构型模式》,原来这么简单

代码实现如下:

图文并茂走进《结构型模式》,原来这么简单

这样子就是用了 外观模式,客户不同跟具体某个家居交互,直接跟音箱交互即可,然后让我们扒一扒 外观模式 的优缺点吧!

优点:

  1. 降低了子系统与客户端之间的耦合度,使得子系统的变化不会影响调用它的客户类
  2. 对客户屏蔽了子系统组件,减少了客户处理的对象数目,并使得子系统使用起来更加容易

缺点:

  1. 不符合开闭原则,修改起来较为麻烦

6)组合模式

组合模式又称为整体模式,是用于把一组相似的对象当作一个单一的对象。组合模式依据树形结构来组合对象,用来表示部分以及整体层次。这种类型的设计模式属于结构型模式,它创建了对象组的树形结构。

文字看的麻烦,用张图你就知道了

图文并茂走进《结构型模式》,原来这么简单

这种就是一个文件系统,而这种结构我们也称为 树形结构。这里面用到的便是 组合模式

组合模式(Composite),将对象组合成树形结构以表示 "部分-整体" 的层次结构,用户对单个对象和组合对象的使用具有一致性。所以当我们的案例是 树形结构部分-整体 的关系时,就可以考虑使用组合模式

其中 组合模式 又有两种不同的实现,分别是 透明模式 和 安全模式,其中角色都是一致的,如下:

1. 透明模式

我们看下透明模式的 UML图:

图文并茂走进《结构型模式》,原来这么简单

透明模式是把使用的方法放到抽象类中,不管是 树枝对象 还是 叶子对象 都有相同的结构,代码如下:

图文并茂走进《结构型模式》,原来这么简单

这样做的好处便是叶子节点和树枝节点对于外界来说没有区别,它们具备完全一致的行为接口,但是 Leaf 类 本身不具备 add() 和remove() 方法的功能,所以实现它是没有意义的,这样子就又出现了另外一种模式,便是 组合模式 - 安全模式

2. 安全模式

安全模式UML 图如下:

图文并茂走进《结构型模式》,原来这么简单

安全模式是把树枝节点和树叶节点彻底分开,树枝节点单独拥有用来组合的方法,这种方法比较安全。代码实现如下:

图文并茂走进《结构型模式》,原来这么简单

这种方式由于不够透明,树叶节点和树枝节点将不具有相同的接口,客户端的调用需要做相应的判断,不能完全整堆抽象编程,必须有区别地对待叶子构件和容器构件。

7)享元模式

运用共享技术来有效地支持大量细粒度对象的复用。它通过共享已经存在的对象来大幅度减少需要创建的对象数量,避免大量相似对象的开销,从而提高系统资源的利用率。

享元模式(Flyweight)中存在两种状态:

享元模式的实现要领就是区分应用中的这两种状态,并将外部状态外部化。

享元模式在开发中运行到的场景挺多的,首先能想到的便是各种 池技术了,线程池、数据库连接池、String常量池等等,所以说 享元模式 是池技术的重要实现方式。

所以 享元模式的用处很简单,便是减少对象的创建

图文并茂走进《结构型模式》,原来这么简单

享元模式 中存在着这几种角色:

接下来我们用代码来实现一下:

图文并茂走进《结构型模式》,原来这么简单

这样子就简单实现了 享元模式,我们老样子来扒一扒 享元模式 的优缺点

优点:

缺点:

为了使对象可以共享,需要将享元对象的部分状态外部化,分离内部状态和外部状态,使程序逻辑复杂

上一篇下一篇

猜你喜欢

热点阅读