1、图解 java 设计模式(四)——彻底弄明白适配模式,桥接模式
1.1、前言
大家可以去 b 站观看韩老师的视频:b站韩老师视频链接
1.2、适配器设计模式
1.2.1、现实生活中的适配器例子
泰国插座用的是两孔(欧标),可以买一个多功能转换插头(适配器),这样就可以使用了。
1.2.2、基本介绍
- 适配器模式(Adapter Pattern)将某个类的接口转换成客户端期望的另一个接口表示,主要的目的是兼容性,让原本因接口不匹配不能一起工作的两个类可以协同工作。其别名为包装器(Wrapper)
- 适配器模式属于结构型模式
- 着急要分为三类:类适配器模式、对象适配器模式、接口适配器模式
1.2.3、工作原理
-
适配器模式:将一个类的接口转换成另一个接口,让原本接口不兼容的类可以兼容 -
从用户的角度看不到被适配者,是解耦的 -
用户调用适配器转化出来的目标接口方法,适配器再调用被适配者的相关接口方法 -
用户收到反馈结果,感觉只是和目标接口交互,如图
1.2.4、类适配器模式
1.2.4.1、类适配器模式介绍
Adapter 类,通过继承 src 类,实现 dst 类接口,完成 src-》dst 的适配
1.2.4.2、类适配器模式应用实例
1.2.4.2.1、应用实例说明
以生活中充电器的例子来说明适配器,充电器本身相当于 adapter,220V 交流电相当于 src(即被适配者),我们的 dst(即目标)是 5V 直流电
1.2.4.2.2、思路分析图
1.2.4.2.3、代码演示
package com.hjc.demo1.adapter.classadapter;
public class Client {
public static void main(String[] args) {
System.out.println("======类适配器模式=========");
Phone phone = new Phone();
phone.charging(new VoltageAdapter());
}
}
package com.hjc.demo1.adapter.classadapter;
public class VoltageAdapter extends Voltage220V implements IVoltage5V{
@Override
public int outPut5V() {
int srcV = outPut220V();
int dstV;
dstV = srcV/44;
return dstV;
}
}
package com.hjc.demo1.adapter.classadapter;
public class Voltage220V {
public int outPut220V() {
int src = 220;
System.out.println("电压=" + src + "V");
return src;
}
}
package com.hjc.demo1.adapter.classadapter;
public interface IVoltage5V {
public int outPut5V();
}
package com.hjc.demo1.adapter.classadapter;
public class Phone {
public void charging(IVoltage5V iVoltage5V){
if (iVoltage5V.outPut5V() == 5){
System.out.println("电压为 5V,可以充电");
}else if (iVoltage5V.outPut5V() >5){
System.out.println("电压大于 5V。不能充电");
}
}
}
1.2.4.2.4、代码截图
1.2.4.3、类适配器模式优缺点
- Java 是单继承机制,所以类适配器需要继承 src 类这一点算是一个缺点,因为这要求 dst 必须是接口,有一定局限性
- src 类的方法在 Adapter 中都会暴露出来,增加了使用的成本
- 由于继承了 src 类,所以它可以更加需求重写 src 类的方法,使得 Adapter 的灵活性增加了。
1.2.5、对象适配器模式
1.2.5.1、对象适配器模式介绍
- 基本思路和类的适配器模式相同,只是将 Adapter 类作修改,不是继承 src 类,而是持有 src 类的实例,以解决兼容性的问题。即:持有 src 类,实现 dst 类接口,完成 src-》dst 的适配
- 根据“合成复用原则”,在系统中尽量使用 关联关系(聚合)来代替继承关系
- 对象适配器模式是适配器模式常用的一种
1.2.5.2、对象适配器模式应用实例
1.2.5.2.1、应用实例说明
以生活中充电器的例子来说明适配器,充电器本身相当于 Adapter,220V 交流电相当于 src(被适配者,)我们的 dst(目标)是 5V直流电,使用对象适配器模式完成
1.2.5.2.2、思路分析图
1.2.5.2.3、代码演示
package com.hjc.demo1.adapter.objadapter;
public class Phone {
public void charging(IVoltage5V iVoltage5V){
if (iVoltage5V.outPut5v() == 5){
System.out.println("电压为 5V,可以充电");
}else if (iVoltage5V.outPut5v() >5){
System.out.println("电压大于 5V。不能充电");
}
}
}
package com.hjc.demo1.adapter.objadapter;
public class VolatgeAdapter implements IVoltage5V{
private Voltage220V voltage220V;
public VolatgeAdapter(Voltage220V voltage220V) {
this.voltage220V = voltage220V;
}
@Override
public int outPut5v() {
int dst = 0;
if (null != voltage220V){
int src = voltage220V.outPut220V();
System.out.println("使用对象适配器,进行适配~");
dst = src/44;
System.out.println("适配完成,输出电压为="+src);
}
return dst;
}
}
package com.hjc.demo1.adapter.objadapter;
public class Voltage220V {
public int outPut220V() {
int src = 220;
System.out.println("电压=" + src + "V");
return src;
}
}
package com.hjc.demo1.adapter.objadapter;
public interface IVoltage5V {
public int outPut5v();
}
package com.hjc.demo1.adapter.objadapter;
public class Phone {
public void charging(IVoltage5V iVoltage5V){
if (iVoltage5V.outPut5v() == 5){
System.out.println("电压为 5V,可以充电");
}else if (iVoltage5V.outPut5v() >5){
System.out.println("电压大于 5V。不能充电");
}
}
}
1.2.5.2.4、代码截图
1.2.5.3、对象适配器模式优缺点
- 对象适配器和类适配器其实算是同一种思想,只不过实现方式不同
- 根据合成复用原则,使用组合代替继承,所以它解决了类适配器必须继承 src 的局限性问题,也不再要求 dst 必须方式接口
- 使用成本更低,更灵活
1.2.6、接口适配器模式
1.2.6.1、接口适配器模式介绍
- 一些书籍成为:适配器模式(Default Adapter Pattern)或缺省适配器模式
- 核心思路:当 不需要全部实现接口提供的方法时,可先 设计一个抽象类实现接口,并为该接口中每个方法提供一个 默认实现(空方法),那么该 抽象类的子类可有选择地覆盖父类的某些方法来实现需求
- 适用于一个接口不想使用其他所有的方法的情况
1.2.6.2、接口适配器模式应用实例
1.2.6.2.1、案例一
-
Android 中的属性动画 ValueAnimator 类可以通过 addListener(AnimatorListenter listener)方法添加监听器,那么常规写法如下 -
有时候我们不想实现 Animator.AnimatorListener 接口的全部方法,我们只想监听 onAnimationStart,我们会如下写 -
AnimatorListenerAdapter 类,就是一个接口适配器 -
AnimatorListener 是一个接口 -
程序里的匿名累不了就是 Listener具体实现类
1.2.6.2.2、图解
1.2.6.2.3、代码演示
package com.hjc.demo1.adapter.interadapter;
public class Client {
public static void main(String[] args) {
System.out.println("======接口适配器模式======");
Abstract a = new Abstract(){
@Override
public void m1() {
System.out.println("使用了m1 方法~~~");
}
};
a.m1();
}
}
package com.hjc.demo1.adapter.interadapter;
public class Abstract implements Interface4{
@Override
public void m1() {
}
@Override
public void m2() {
}
@Override
public void m3() {
}
@Override
public void m4() {
}
}
package com.hjc.demo1.adapter.interadapter;
public interface Interface4 {
public void m1();
public void m2();
public void m3();
public void m4();
}
1.2.6.2.4、代码截图
1.2.7、适配器模式在 SpringMVC 源码剖析
-
SpringMVC 中的 HandlerAdapter,就是使用了适配器模式 -
SpringMVC 处理请求的流程回顾 -
使用 HandlerAdapter 的原因分析 可以看到处理器的类型不同,有多重实现方式,那么调用方式就不是确定的,如果需要直接调用 Controller 方法,需要调用的时候就得不断是使用 if else 来进行判断是哪一种子类然后执行。那么如果后面要扩展 Controller,就得修改原来的代码,违背了 OCP原则 -
代码分析 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CedNb0Ez-1637506410472)(/Users/hjc/Library/Application Support/typora-user-images/image-20211121210936258.png)] -
这里动手写的 SpringMVC 通过适配器设计模式获取对应的 Controller 源码这里就不展示,太多了各位兄弟们可以直接去看视频
1.2.8、适配器模式的注意事项
-
三种命名方式,是根据 src 是以怎样的形式给到 Adapter(在 Adapter 里的形式)来命名的 -
类适配器:以类给到,在 Adapter 里,就是将 src 当做类,继承 对象适配器:以对象给到,在 Adapter 里,将 src 作为一个对象,持有 接口适配器:以接口给到,在 Adapter 里,将 src 作为一个接口,实现 -
Adapter 模式最大的作用还是将原本不兼容的接口融合在一起工作 -
实际开发中,实现起来不拘泥于讲解到的三种经典形式
1.3、桥接模式
1.3.1、手机操作问题
现在对不同手机类型的不同品牌实现操作编程(比如:开机、关机、打电话等等)
1.3.2、传统方式解决手机操作问题
1.3.3、传统方法解决手机操作问题分析
- 扩展性问题(类爆炸),如果我们再增加手机的样式(旋转式),就需要增加各个品牌手机的类,同样如果我们增加一个手机品牌,也要在各个手机样式类下增加
- 违反了单一职责原则,当我们增加手机样式时,要同时增加所有品牌的手机,这样增加了代码维护成本
- 解决方案-使用桥接模式
1.3.4、桥接设计模式(Bridge)
1.3.4.1、基本介绍
- 桥接模式是指:将实现与抽象放在两个不同的类层次中,使两个层次可以独立改变
- 是一种结构型设计模式
- Bridge 模式基于类的最小设计原则,通过使用封装、聚合及继承等行为让不同的类承担不同的职责。它的主要特点是把抽象(Abstranction)与行为实现(Implementation)分离开来,从而可以保持各部分的独立性以及应对他们的功能扩展
1.3.4.2、原理类图
说明:
- Client 类:桥接模式的调用者
- 抽象类(Abstraction):维护了 Implementor/即它的实现类 ConcreteImplementorA…,两者是聚合关系,Abstraction 充当桥接类
- RefinedAbstraction:是Abstraction 抽象类的子类
- Implementor:行为实现类的接口
- ConcreteImplementorA/B:行为的具体实现类
- 从图中:这里的抽象类和接口是聚合的关系,其实调用和被调用的关系
1.3.5、桥接模式解决手机操作问题
使用桥接模式改进传统方式,让程序具有更好的扩展性,已用程序维护
1.3.5.1、类图
1.3.5.2、代码演示
package com.hjc.demo1.bridge;
public class Client {
public static void main(String[] args) {
System.out.println("=====桥接设计模式=====");
Phone phone = new FoldedPhone(new Xiaomi());
phone.open();
phone.close();
phone.call();
System.out.println("===================");
Phone phone1 = new UpRightPhone(new Vivo());
phone1.open();
phone1.close();
phone1.call();
}
}
package com.hjc.demo1.bridge;
public class FoldedPhone extends Phone{
public FoldedPhone(Brand brand) {
super(brand);
}
public void open(){
super.open();
System.out.println("折叠式手机开机");
}
public void close(){
super.close();
System.out.println("折叠式手机关机");
}
public void call(){
super.close();
System.out.println("折叠式手机打电话");
}
}
package com.hjc.demo1.bridge;
public class UpRightPhone extends Phone{
public UpRightPhone(Brand brand) {
super(brand);
}
public void open(){
super.open();
System.out.println("直立式手机开机");
}
public void close(){
super.close();
System.out.println("直立式手机关机");
}
public void call(){
super.close();
System.out.println("直立式手机打电话");
}
}
package com.hjc.demo1.bridge;
public class Phone {
private Brand brand;
public Phone(Brand brand) {
this.brand = brand;
}
protected void open(){
this.brand.open();
}
protected void close(){
this.brand.close();
}
protected void call(){
this.brand.call();
}
}
package com.hjc.demo1.bridge;
public interface Brand {
public void open();
public void close();
public void call();
}
package com.hjc.demo1.bridge;
public class Vivo implements Brand{
@Override
public void open() {
System.out.println("Vivo 手机开机");
}
@Override
public void close() {
System.out.println("Vivo 手机关机");
}
@Override
public void call() {
System.out.println("Vivo 手机打电话");
}
}
package com.hjc.demo1.bridge;
public class Xiaomi implements Brand{
@Override
public void open() {
System.out.println("小米手机开机");
}
@Override
public void close() {
System.out.println("小米手机关机");
}
@Override
public void call() {
System.out.println("小米手机打电话");
}
}
1.3.5.3、代码截图
1.3.6、桥接模式在 jdbc 的源码分析
-
jdbc 的Driver接口,如果从桥接模式来看,Driver 就是一个接口,下面可以有 MySQL 的 Driver,Oracle 的 Driver,这些就可以当做实现接口类 -
代码分析 -
对 jdbc 源码分析的类图
1.3.7、桥接模式的注意事项
- 实现了抽象和实现部分的分离,从而极大的提供了系统的灵活性,让抽象部分和实现部分独立开来,这有助于系统进行分层设计,从而产生更好的结构系统
- 对于系统的高层部分,只需要知道抽象部分和实现部分的接口就可以了,其他的部分由具体业务来完成
- 桥接模式替换多层继承方案,可以减少子类的个数,降低系统的管理和维护成本
- 桥接模式的引入增加了系统的理解和涉及难度,由于聚合关联关系建立在抽象层,要求开发者针对抽象进行设计和编程
- 桥接模式要求正确识别出系统中两个独立变化的维度(抽象、实现),因此其使用范围有一定的局限性,即需要有这样的应用场景
- 对于那些不希望使用继承或因为多层继承导致系统类的个数急剧增加的系统,桥接模式尤为适用
1.3.8、场景的应用场景
-
jdbc 的驱动程序 -
银行的转账系统 转账分类:网上转账、柜台转账、AMT 转账 转账用户类型:普通用户、银卡用户、金卡用户 -
消息管理 消息类型:即时消息、延时消息 消息分类:手机短信、邮件消息、QQ 消息
|