结构型设计模式
文章目录
- 结构型设计模式
- 一、概述
- 二、适配器模式(Adapter Pattern)
- 2.1 类适配器模式
- 2.2 对象适配器模式
- 2.3 接口适配器模式
- 2.4 小结
- 三、桥接模式(Bridge Pattern)
- 四、装饰器模式(Decorator Pattern)
- 五、组合模式(Composite Pattern)
- 六、外观模式(Facade Pattern)
- 七、享元模式(Flyweight Pattern)
- 八、代理模式(Proxy Pattern)
- 九、依赖注入模式 *(Dependency Injection)
- 十、流接口模式 *(Fluent Interface)
一、概述
这些设计模式关注类和对象的组合。继承的概念被用来组合接口和定义组合对象获得新功能的方式。
- 适配器模式(Adapter Pattern)
- 桥接模式(Bridge Pattern)
- 装饰器模式(Decorator Pattern)
- 组合模式(Composite Pattern)
- 外观模式(Facade Pattern)
- 享元模式(Flyweight Pattern)
- 代理模式(Proxy Pattern)
- 依赖注入模式 *(Dependency Injection)
- 流接口模式 *(Fluent Interface)
二、适配器模式(Adapter Pattern)
- 适配器模式(Adapter Pattern)将某个类的接口转换成客户端期望的另一个接口表示,主的目的是兼容性,让原本因接口不匹配不能一起工作的两个类可以协同工作。其别名为包装器(Wrapper)
- 主要分为三类:类适配器模式、对象适配器模式、接口适配器模式
2.1 类适配器模式
Adapter类,通过继承 src类,实现 dst 类接口,完成src->dst的适配。
以手机充电器为例子:
充电器本身相当于Adapter,220V交流电相当于src (即被适配者),dst(即目标)是5V直流电
2.2 对象适配器模式
- 基本思路和类的适配器模式相同,只是将Adapter类作修改,不是继承src类,而是持有src类的实例,以解决兼容性的问题。 即:持有 src类,实现 dst 类接口,完成src->dst的适配
- 根据“合成复用原则”,在系统中尽量使用关联关系来替代继承关系
- 对象适配器模式是适配器模式常用的一种
public class VoltageAdapter2 implements Voltage5 {private Voltage220 voltage220; //持有Voltage220对象,不是继承了
}
2.3 接口适配器模式
当不需要全部实现接口提供的方法时,可先设计一个抽象类实现接口,并为该接口中每个方法提供一个默认实现(空方法),那么该抽象类的子类可有选择地覆盖父类的某些方法来实现需求。在使用Java做页面监听时经常会碰到对应适配类。
2.4 小结
- 三种命名方式,是根据 src是以怎样的形式给到Adapter(在Adapter里的形式)来命名的。
- 类适配器:以类给到,在Adapter里,就是将src当做类,继承
- 对象适配器:以对象给到,在Adapter里,将src作为一个对象,持有
- 接口适配器:以接口给到,在Adapter里,将src作为一个接口,实现
- Adapter模式最大的作用还是将原本不兼容的接口融合在一起工作。
- 实际开发中,实现起来不拘泥于这三种经典形式
三、桥接模式(Bridge Pattern)
- 桥接模式(Bridge 模式)是指:是将抽象部分与它的具体实现部分分离,使它们都可以独立地变化。
- Bridge 模式基于类的最小设计原则,通过组合/聚合的方式建立两个类之间的联系,而不是继承。但又类似于多重继承方案,但是多重继承方案往往违背了类的单一职责原则,其复用性较差,桥接模式是比多重继承更好的替代方案。桥接模式的核心在于解耦抽象和实现。
- Client 类:桥接模式的调用者
- 抽象类(Abstraction) :维护了 Implementor / 即它的实现类 ConcreteImplementorA…, 二者是聚合关系, Abstraction充当桥接类
- RefinedAbstraction : 是 Abstraction 抽象类的子类
- Implementor : 行为实现类的接口
- ConcreteImplementorA /B :行为的具体实现类
- 从 UML 图:这里的抽象类和接口是聚合的关系,其实是调用和被调用关系
上面概念比较抽象,这里以手机为例:
- 首先有需求,手机可以按品牌分:HuaWei、Vivo、XiaoMi,不同品牌对应着不同的应用商城
- 业务需求变更,对于手机型号有了区分,分为A、B、C型,不按桥接模式设计,则需要多重继承实现功能全排序,不同品牌对应不同型号共9个实现
- 按桥接模式设计,新建一个桥接抽象类为Phone,其实现类分别为A、B、C型,有不同的功能,通过聚合关系,将品牌实现接口聚合到Phone抽象类中,这样只需新增3个实现类;而手机的型号以及品牌又可以区分开来,分别运用其不同特别功能
常见应用场景:
- JDBC 驱动程序
- 银行转账系统
- 转账分类: 网上转账,柜台转账,AMT 转账
- 转账用户类型:普通用户,银卡用户,金卡用户
- 消息管理
- 消息类型:即时消息,延时消息
- 消息分类:手机短信,邮件消息,QQ 消息…
四、装饰器模式(Decorator Pattern)
装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其结构。
典型的应用就是jdk中IO流的应用,FilterInputStream 就是一个装饰者,BufferInputStream是具体的实现类,通过组合的关系,使得输入流有了缓冲的功能,而又无需修改原有inputStream代码。
五、组合模式(Composite Pattern)
组合模式就运用了树形结构,该模式的核心思想是:将多个对象组合成树形结构,以此结构来表示“整体-部分”之间的层次关系。
- 抽象根节点(Component):定义系统各层次对象的共有方法和属性,可以预先定义一些默认行为和属性
- 树枝节点(Composite):定义树枝节点的行为,存储子节点,组合树枝节点和叶子节点形成一 个树形结构
- 叶子节点(Leaf):叶子节点对象,其下再无分支,是系统层次遍历的最小单位
应用场景:
- 组合-部分整体场景:将多个对象组合成一个整体,并且整体与部分是一致对待的。比如树形菜单、文件夹和文件等
- 递归结构场景:处理递归的数据结构。比如文件和目录的关系就是递归的
- 规则场景:当需要处理的对象具有明显的层次结构时,可以考虑使用组合模式。比如,企业中不同职位的员工,每个职位的员工都有一些共同的属性(比如姓名、公司邮件地址),但也有一些不同的属性(比如职位、薪水等)
- 任务分解场景:将一个大任务分解为多个小任务,然后再将小任务组合起来,形成一个任务树。这种情况下,可使用组合模式对任务进行建模
- GUI控件场景:GUI控件通常可以嵌套在其他控件中,并且用户可以在控件中插入其他控件,组合模式适用于GUI控件的场景
组合模式适用于处理树形结构的场景,将一个对象的部分和整体看作一样,形成一个树形结构。在需要统一处理整个树形结构时,可以考虑使用组合模式。
六、外观模式(Facade Pattern)
外观模式(Facade),也叫过程模式:外观模式为子系统中的一组接口提供一个一致的界面,此模式定义了一个高层接口。
外观模式通过定义一个一致的接口,用以屏蔽内部子系统的细节,使得调用端只需跟这个接口发生调用,而无需关心这个子系统的内部细节。这个模式我们平时就会使用,比如调用交易接口,交易接口中会再去调用其他各个接口。或者MVC模式下我们写的Controller,即对外暴露接口,前端开发无需关心后端干了什么。
- 通过合理的使用外观模式,可以帮我们更好的划分访问的层次
- 在维护一个遗留的大型系统时,可能这个系统已经变得非常难以维护和扩展,此时可以考虑为新系统开发一个 Facade 类,来提供遗留系统的比较清晰简单的接口,让新系统与 Facade 类交互,提高复用性
- 不能过多的或者不合理的使用外观模式,使用外观模式好,还是直接调用模块好。要以让系统有层次,利于维护为目的
七、享元模式(Flyweight Pattern)
享元模式(Flyweight Pattern) 也叫 蝇量模式: 运用共享技术有效地支持大量细粒度的对象。
享元模式经典的应用场景就是池技术了,String 常量池、数据库连接池、缓冲池等等都是享元模式的应用,享元模式是池技术的重要实现方式
- FlyWeight 是抽象的享元角色, 他是产品的抽象类, 同时定义出对象的外部状态和内部状态(后面介绍) 的接口或实现
- ConcreteFlyWeight 是具体的享元角色,是具体的产品类,实现抽象角色定义相关业务
- UnSharedConcreteFlyWeight 是不可共享的角色,一般不会出现在享元工厂
- FlyWeightFactory 享元工厂类,用于构建一个池容器(集合), 同时提供从池中获取对象方法,这个一般被设计为单例模式
内部状态是不会变化的,可以被多个对象共享,而外部状态会随着对象的使用而改变。比如,连接池中的连接对象,保存在连接对象中的用户名、密码、连接URL等信息,在创建对象的时候就设置好了,不会随环境的改变而改变,这些为内部状态。而当每个连接要被回收利用时,我们需要将它标记为可用状态,这些为外部状态。
八、代理模式(Proxy Pattern)
代理模式:为一个对象提供一个替身,以控制对这个对象的访问。即通过代理对象访问目标对象。这样做的好处是:可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能。
代理模式有不同的形式, 主要有三种 静态代理、动态代理 (JDK 代理、接口代理)和 Cglib 代理 (可以在内存动态的创建对象,而不需要实现接口, 他是属于动态代理的范畴)。最经典的运用即Spring的AOP。
九、依赖注入模式 *(Dependency Injection)
依赖注入(Dependency Injection)是控制反转(Inversion of Control)的一种实现方式。
我们先来看看什么是控制反转。当调用者需要被调用者的协助时,在传统的程序设计过程中,通常由调用者来创建被调用者的实例,但在这里,创建被调用者的工作不再由调用者来完成,而是将被调用者的创建移到调用者的外部,从而反转被调用者的创建,消除了调用者对被调用者创建的控制,因此称为控制反转。
要实现控制反转,通常的解决方案是将创建被调用者实例的工作交由 IoC 容器来完成,然后在调用者中注入被调用者(通过构造器/方法注入实现),这样就实现了调用者与被调用者的解耦,该过程被称为依赖注入。依赖注入不是目的,它是一系列工具和手段,最终的目的是帮助开发出松散耦合(loose coupled)、可维护、可测试的代码和程序。这条原则的做法是大家熟知的面向接口,或者说是面向抽象编程。典型应用案例即Spring中使用@Autowize
注解实现属性注入。
个人感觉这种模式和注册模式类似,都是通过统一容器收纳注册对象,调用方直接通过注册容器调用接口,具体实现类则由容器返回。故不再对注册模式分析。
十、流接口模式 *(Fluent Interface)
在软件工程中,流接口(Fluent Interface)是指实现一种面向对象的、能提高代码可读性的 API 的方法,其目的就是可以编写具有自然语言一样可读性的代码,我们对这种代码编写方式还有一个通俗的称呼 —— 方法链。
最常见的就是Lombak注解中,@Builder
或者 @Accessors(chain=true)
开启链式编程,即可通过方法链调用的方式设置实体类的属性。
// 用简洁的方式实例化实体类并完成赋值操作,而无需多行不断调用set方法
XUser xUser = XUser.builder().userId(1).userName("AAAAA").build();