桥接模式可以将系统中稳定的部分和可扩展的部分解耦,使得系统更加容易扩展,且满足OCP原则,对调用者修改关闭。避免新老模块代码混编,大大降低开发测试难度。
普通设计带来的问题:
1.增加新模块后,会修改老模块的代码,增加开发和测试的难度。
2.项目中稳定的部分和可扩展的部分没有解耦,新模块对老方法重复实现。
小宝最近接受了一个新功能。一个需要和 公司A 传输对账文件的功能
需求如下
1.对账文件有专门核心业务系统生成,会放在本公司的一台云服务器上(对内),通过docId关联。 小宝只需要通过docId将文件从云服务器上下载到本系统。而不用关心文件是怎么生成的。
2.专门核心业务系统(内部的外系统)生成完文件后,会将文件放在云服务器上,并发送一条MQ消息通知本系统是哪个docId。
3.公司A提供了本系统一台FTP服务器,小宝需要将本系统的对账文件传到公司A的FTP服务器上。对账功能由公司A做,本系统也不用关心。
流程设计:
1.本系统消费核心系统的MQ消息,得到文件ID,docId
2.通过docId从云服务器上获得对账文件file,存在本系统的本地目录中
3.本系统登陆公司A提供的文件服务器,将file文件put上去。
于是小宝做了第一版
comsumerMq(mqMsg: String)
消费MQ消息,得到docId文件ID
downFile(docId: String)
通过docId从内部的云服务器得到文件下载到本系统的本地目录。
upLoadFile()
本系统将对账文件放置到公司A提供的FTP服务器上(外部的)
insert(文件状态: 文件对象)
每消费一笔MQ消息就产生一个文件记录对象落地,记录文件状态-消费成功-下载成功失败-上传成功失败 并记录错误码、文件ID、文件名称方便日后跟踪。
此时小宝花了2天完成了老板的开发任务,但好景不长,两周后,小宝又接到了一个新任务。
又有一个新公司B,也需要和本公司对账。公司B没有提供FTP服务器,而是提供了一个传输文件的接口API(与公司A的方式不同)于是小宝看了一眼需求,来呀,SO easy,料到了,当时设计抽象文件处理接口就是为了今后扩展。
先整理下需求:
1.核心系统还是那个MQ消息通知本系统,MQ消息中通过渠道channel字段来区分是公司A或者公司B的文件。
2.下载的逻辑不变。两家公司的对账文件还是放在原来的云服务器上,通过docId关联。
3.文件上传的逻辑,公司A提供FTP文件服务器,公司B提供远程API,所以这边的upload方法需要分开实现。
4.文件状态表还是这么记录,通过channel渠道字段区分。
流程设计:
1.本系统消费核心系统的MQ消息,得到文件ID,docId 和 代表对应的渠道channel字段用来区分公司A还是公司B
2.通过docId从云服务器上获得对账文件file,存在本系统的本地目录中
3.如果是公司A则本系统登陆公司A提供的文件服务器,将file文件put上去。
如果是公司B则本系统将file转成文件流,调用公司B提供的文件传送API
于是小宝写了第二版。
小宝照着设计图开干了,结果发现掉坑里了,写了大量的代码,虽然功能实现了,但是设计上还是不方便扩展。明明已经用到了抽象接口,WHY!!!问题出在哪里了呢?
问题:
问题1.MQ消息调用者写了大量的代码,且公司A和公司B的代码都混合在了一起,调用者需要判断是哪家公司,然后调用对应的API实现类。
比如 MQ消息调用者必须这么写:
//先通过MQ消息中的channel渠道判断文件是公司A还是公司B
//解析MQ字段
if(公司A){
公司A.comsumerMq();
}else(公司B){
公司B.comsumerMq();
}
//下载文件
if(公司A){
公司A.downFile();
}else(公司B){
公司B.downFile();
}
//上传文件
if(公司A){
公司A.upLoadFile();
}else(公司B){
公司B.upLoadFile();
}
问题2:下载文件接口公司A和公司B的处理方式一模一样,但是两边都重新实现了,代码冗余。即使可以将文件下载变成公共方法,但是公司A、公司B都要分开实现。就是说“文件”和“公司”两个类之间的关系没有解耦。
问题3:这个设计不满足OCP原则,他对调用者没有修改关闭。当增加一个新公司后,调用者代码得大幅度改造,增加很多if else分支判断,而且当时公司A的代码已经测试通过,新增公司B又一定会动公司A的代码。实际小宝慌得很,喊测试叫爸爸,各位测试老师,就辛苦你们,公司B代码上了后,不仅公司B需要测,公司A也需要再测。
此时,老板给了2天时间,小宝用了3天时间才改完调试完,而且还动了原来公司A的代码,于是小宝慌了,如果再接扩展,岂不是又重复一遍累死。
综上所述:这种设计最大的问题就是不满足OCP原则,当有新需求改造后,本应该没有关系的新老模块没有解耦,导致代码混编,不方便扩展。
此时小宝选择了设计模式中的-桥接模式进行改造,将公司与文件解耦,于是做了第三版。
新增文件处理逻辑层类,而逻辑层有个私有的抽象文件接口对象与抽象文件处理接口进行桥接。
逻辑层代码如下
private 抽象文件接口API;
void set实现I(具体各家公司的实现){
抽象文件接口API = 具体各家公司的实现;
}
publid String comsumerMq(mqMsg:String){
//解析MQ字段
抽象文件接口API.comsumerMq();
//下载文件
this.downFile();
//上传文件
抽象文件接口API.upLoadFile();
}
//公共的文件下载实现
private void downFile(docId:String){
//从云服务器下载文件
}
而调用者的逻辑也简单了
private 抽象文件接口API 公司A实现;
private 抽象文件接口API 公司B实现;
private 文件处理逻辑层 逻辑层;
void action(mqMsg:String){
//通过mqMsg判断是哪家公司的文件。
//为逻辑层定义具体是哪个实现类
if(公司A){
逻辑层.set实现(公司A实现);
}else if(公司B){
逻辑层.set实现(公司B实现);
}else{
throw 未来其他公司未实现不处理
}
//真正实现类去干活
逻辑层.comsumerMq(mqMsg);
}
}
我们可以发现,如果再增加一个公司C,那么整个框架的绿色部分是不用修改的,只需要增加红色的部分即可。开发和测试难度降低。
而没有用桥接模式,导致所有的业务逻辑全都压在调用者身上,新老模块代码混编。
可以看出桥接模式针对4个问题都做了对应的改进。
1.MQ消息调用者逻辑简单,改动小。
2.文件与公司解耦,文件下载与那家公司是没有关系的,所以公司实现类就不用实现下载接口。
3.满足OCP原则,对外的接口调用修改关闭。逻辑层对调用者提供的接口没有修改。而调用者只需要指定是哪个具体实现类即可。
1.桥接模式可以将系统中稳定的部分和可扩展的部分解耦,使得系统更加容易扩展,且满足OCP原则,对调用者修改关闭。