设计模式——结构型

一、代理模式(Proxy Design Pattern)

1、代理模式的原理与实现

在不改变原始类(或叫被代理类)的情况下,通过引入代理类给原始类附加功能。

一般情况下,我们让代理类和原始类实现同样的接口,但是,如果原始类并没有定义接口,并且原始类代码并不是我们开发维护的,在这种情况下,可以通过代理类继承原始类的方法来实现代理模式。

2、动态代理的原理与实现

静态代理需要针对每个类都创建一个代理类,并且每个代理类中的代码都有点像模板模式的“重复”代码,增加了维护成本和研发成本。
动态代码即不事先为每个原始类编写代理类,而是在运行的时候动态地创建原始类对应的代理类,然后在系统中用代理类替换掉原始类。

Spring AOP底层的实现原理就是基于动态代理,用户配置好需要给哪些类创建代理,并定义好在执行原始类的业务代码前后执行哪些附加功能,Spring为这些类创建动态代理对象,并在JVM中替代原始类对象。原本在戴帽子执行的原始类的方法,被换作执行代理类的方法,也就实现了给原始类添加功能的目的。

3、代理模式的应用场景

  • 业务系统的非功能性需求开发,如监控、统计、鉴权、限流、事务、幂等、日志。将这些附加功能与业务功能解耦,放在代理类统一处理。
  • 还可应用在RPC、缓存等应用场景中。
    RPC框架也可以看作一种代理模式,通过远程代码,将网络通信数据等细节隐藏起来,客户端使用RPC服务时就像使用本地函数一样,无需了解原服务器交互的细节。
    Spring框架可以在AOP切面中完成接口缓存的功能,在应用启动的时候,从配置文件中加载需要支持需要支持缓存的接口,以及相应的缓存策略等。当请求到来的时候,在AOP切面中拦截请求,如果请求中带有支持缓存的字段,便从缓存中获取数据直接返回。

二、桥接模式(桥梁模式)(Bridge Design Pattern)

1、桥接模式定义

Decouple an abstraction from its implementation so that two can vary independently.

  • 定义1:将抽象和实现解耦,让它们可以独立变化。
  • 定义2:一个类存在两个(或多个)独立变化的维度,我们通过组合的方式,让两个(或多个)维度可以独立进行扩展。
  • 定义1中抽象指的并非“抽象类”或“接口”,而是被抽象出来的一套“类库”。它只包含骨架代码,真正的业务逻辑委派给定义中的“实现”来完成;而“实现”也并非“接口的实现类”,而是一套独立的“类库”。
  • 定义2中类似于“组合优于继承”的设计原则,通过组合关系来替代继承关系,避免继承层次的指数级爆炸。

2、桥接模式应用场景

业务抽象角色引用业务实现角色,或者说,业务抽象角色的部分实现是由业务实现角色完成的。

继承可以把公共的方法或属性抽取,父类封装共性,子类实现特性。但是强关联关系,父类有的方法,子类必须要有,是不可选择的。
桥梁模式描述了类之间的弱关联关系。
对于比较明确不发生变化的,通过继承来完成;若不能确定是否会发生变化的,那就任务是会发生变化的,则通过桥接模式。

如:数据库驱动Driver、JDBC驱动是桥接模式的经典应用。

三、装饰器模式(Decorator Pattern)

1、装饰器模式原理与实现

装饰器模式主要解决了继承关系过于复杂的问题,通过组合来替换继承,它的主要作用是给原始类添加增强功能,这也是判断是否该用装饰器模式的一个重要依据。除此之外,装饰器模式有一个特点,那就是可以对原始类嵌套使用多个装饰器。为了满足这个应用场景,在设计的时候,装饰器内需要跟原始类继承相同的抽象类或者接口。

2、装饰器模式应用场景

从JAVA IO的设计来看,装饰器模式相对于简单的组合关系有两个比较特殊的地方:

  • 装饰器类的和原始类继承同样的父类,这样可以对原始类“嵌套”多个装饰器,FileInputStream嵌套了两个装饰器类:BufferedInputStream和DataInputeStream。让它既支持缓存读取,又支持按照基本数据类型来读取数据

  • 装饰器类是对功能的增强,这也是装饰器模式应用场景的一个重要特点。

代理模式中代理类附加的是跟原始类无关的功能,而在装饰器模式中,装饰器内附加的是跟原始类相关的增强功能。

四、适配器模式(Adapter Design Pattern)

1、适配器模式原理与实现

适配器模式是用来做适配,它将不兼容的接口转换为可兼容的接口,让原本由于接口不兼容而不能在一起工作的类可以一起工作。

适配器模式的两种实现方式:类适配器和对象适配器。
其中类适配器使用继承关系来实现。对象适配器使用组合关系实现。一般来说,适配器模式可以看做一种“补偿模式”,用来补救设计上的缺陷,如果在设计初期就能协调规避接口不兼容的问题。那这种模式就没有应用的机会了。

2、适配器的使用场景:

  • 封装有缺陷的接口设计
    假设我们依赖的外部系统在接口设计方面有缺陷(比如包含大量静态方法),引入之后会影响到我们自身代码的可测试性。为了隔离设计上的缺陷,希望对外部的系统提供的接口进行二次封装。抽象出更好的接口设计,即可使用适配器模式。

  • 统一多个类的接口设计。

  • 替换依赖的外部系统:把项目中依赖的一个外部系统替换为另一个外部系统的时候,利用适配器模式可以减少对代码的改动。

  • 兼容老版本接口。

  • 适配不同格式的数据:
    如Arrays.asList()看作一种数据适配器,将数组类型的数据转化为集合容器类型。
    适配器在Java日志中的应用:日志框架如log4j,logback等都提供了相似的功能:如按不同级别打印日志.
    slf4j提供了统一的接口定义及提供了针对不同日志框架的适配器。

五、门面模式(外观模式)(Facade Design Pattern)

1、门面模式原理与实现

门面模式为子系统提供一组统一的接口,定义一组高层接口让子系统更易用。

假设有一个系统A,提供了a、b 、c、d四个接口,系统B完成某个业务功能需要调用a系统的a,b,c接口,利用门面模式,我们提供一个包裹a,b,c接口调用的门面接口x,给系统B直接使用。

2、门面模式的应用场景

  • 解决易用性问题:
    门面模式让子系统更加易用。
    可以用来封装系统的底层实现,隐藏系统的复杂性,提供一组更加简单易用,更加高层的接口。
    如:Linux系统调用的函数就可以看做一种门面,他是Linux操作系统暴露给开发者的一组“特殊”的编程接口,它封装了底层更基础的Linux内核调用。
    linux的shell命令,实际上也可以看做一种门面模式的应用。他继续封装系统的调用,提供更加友好简单的命令,让我们可以通过执行命令直接跟操作系统交互。类似迪米特法则和接口隔离原则:有两个交互的系统,只暴露有限的必要接口。

  • 解决性能问题
    通过多个接口调用替换为一个门面接口调用,减少网络通信成本,提高App客户端的响应速度。

  • 解决分布式事务问题。
    假设有两个业务模块:用户和钱包。两个业务都对外暴露了一系列接口,在用户注册的时候不仅会创建用户,还会给用户创建一个钱包。这个需求可以通过依次调用用户的创建接口和钱包的创建接口。但用户注册需支持事务,要么成功,要么都失败。
    即可通过引入分布式事务框架或者事后补偿的机制来解决。
    最简单的解决方式是利用数据库事务或者spring框架提供的事物在一个事务中执行创建用户和创建钱包两个SQL操作,即借用门面模式的思想,再设计一个包裹这两个操作的新接口,让新接口在一个事物中执行两个SQL操作。

类模块系统之间的通信一般都是通过接口调用来完成的。接口设计的好坏直接影响到类模块系统是否好用。所以完成接口设计就相当于完成了一半的开发任务,只要接口设计的好,代码就不会差。

接口力度设计的太大,太小都不好,太大会导致接口不可复用,太小会导致接口不易复用。在实际开发中,接口的可复用性和易用性需要“微妙”的权衡,针对这个问题,应尽量保持接口的可复用性,但针对特殊情况。允许提供冗余的门面接口来提供易用的接口。

六、组合模式(Composite Design Pattern)

1、组合模式原理与实现

将一组对象组织成树形结构,以表示一种“部分-整体”的层次结构组合,让客户端(在很多设计模式书中,代指代码的使用者)可以统一单个对象和组合对象的处理逻辑。

  • 组合模式的设计思路与其说是一种设计模式,倒不如说是业务场景的一种数据结构和算法的抽象。其中数据表示成树这种数据结构业务需求可以通过在树上的递归遍历算法来实现。
  • 组合模式,将一组对象组织成树形结构,将单个对象和组合对象都看成树中的节点,以统一处理逻辑,并且它利用树形结构的特点,递归的处理每个子树,依次简化代码实现。
  • 使用组合模式的前提在于,业务场景必须能够表示成树形结构。

2、组合模式的应用场景

  • 文件系统
  • 构建整个公司的人员架构图(公司的人事管理)

七、享元模式(Flyweight Design Pattern)

1、享元模式的原理与实现

“享元”,即被共享的单元。享元模式的意图是复用对象,节省内存。前提是享元对象是不可变的对象。

当一个系统中存在大量重复对象的时候,这些重复对象是不可变的对象,就可以利用享元模式将对象设计成享元,在内存中只保留一份实例。供多处代码引用。这样可以减少内存中对象的数量,起到节省内存的目的。实际上,相同对象,相似对象也可以将这些对象中相同的部分提取出来,设计成享元,让这些大量相似对象引用这些享元。

“不可变对象”指一旦通过构造函数初始化完成之后,它的状态(对象的成员变量或属性)就不会再被修改了。即不能暴露任何set()等修改内部状态的方法。

实现:
主要是通过工厂模式,在工厂类中,通过一个Map或者List来缓存已经创建好的享元对象,以达到复用的目的。

2、享元模式应用场景

  • 文本编辑器:把字体格式设计成享元,让不同的文字共享使用
  • 棋盘游戏
  • Java Integer 、String 中的应用:具体说明查看博客:Java自动装箱与自动拆箱

3、享元模式 、单例、缓存、对象

  • 单例模式中一个类只能创建一个对象,而享元模式中一个类可以创建多个对象,每个对象被多次代码引用共享。
  • 单例是为了限制对象的个数,享元是为了对象复用,节省内存。
  • 享元模式中,通过工厂类来缓存已经创建好的对象。缓存即存储,而平时所说的“数据库缓存”,“CPU缓存”等是为了提高访问效率,而非复用。
  • 对象池,连接池(数据库连接池),线程池等也是为了复用。
    对象向池内存的管理在c++等语言中是由程序员负责的,避免平凡的进行对象创建和释放,导致内存碎片引入预先申请一片连续的内存空间及对象时每次创建对象时,从对象池中直接取出一个空间对象来使用,对象使用完成之后再放回对象池中,以供后续复用。池化技术的“复用”可以理解为重复使用,主要目的是节省时间,在任意时刻每个对象连接、线程并不会被多处使用,而是被一个使用者独占,使用完成之后放回池中,再由其他使用者重复引用。享元中“复用”理解为共享使用,被所有使用者共享,主要目的是节省空间。

实际上享元模式对jvm的垃圾回收并不友好,因为享元工厂内一直保存了对享元对象的引用,导致享元对象在没有任何代码使用的情况下也不被jvm垃圾回收机制自动回收掉,因此在某些情况下,如生命周期短,不会被密集使用,利用享元反倒会浪费更多的内存,所以不要过度使用,为了一点点的内存节省而引入一个复杂的设计模式。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值