学习目标
1.掌握适配器模式和桥接模式的应用场景
2.重构第三方登录自由适配的业务场景
3.了解适配器模式和桥接模式在源码中的应用
4.适配器模式和桥接模式的优缺点
适配器模式的定义
适配器模式又叫变压器模式,它的功能是将一个类的接口变成客户端所期望的另一种接口,从而使原本因接口不匹配而导致无法在一起工作的两个类能够一起工作
属于结构性模式
适配器的使用场景
1.已经存在的类,它的方法和需求不匹配(方法结构相同或类似)的情况
2.适配器模式不是软件设计阶段考虑的设计模式,是随着软件维护,由于不同产品不同厂家造成功能类似而接口不相同的情况下的解决方案。
主要就是解决兼容问题!!!
适配器主要有三种写法,类适配器,对象适配器,接口适配器
适配器主要有三个角色:目标角色(在原有功能的基础上提出的需求)、原角色(系统中满足客户需求的稳定的功能,但不能兼容现在的需求)、适配器(将现在已有的功能转换成目标功能)
类适配器
场景:将220V电压转换成 5V电压
//原始类
public class AC220 {
public int outputAC220(){
int output = 220;
System.out.println("输出电压是 "+output +" V");
return output;
}
}
//目标角色
public interface DC5 {
//目标接口
int output5V();
}
//适配器
public class PowerAdapter extends AC220 implements DC5{
@Override
public int output5V() {
int adapterIutput = super.outputAC220();
int adapterOutput = adapterIutput / 44 ;
System.out.println("使用适配器输入"+adapterIutput +"V"+",输出"+adapterOutput+"V");
return adapterOutput;
}
}
//测试
public class Test {
public static void main(String[] args) {
PowerAdapter adapter1 = new PowerAdapter();
// DC5 adapter2 = new PowerAdapter();
adapter1.output5V();
//按场景只需要使用一个5V的接口,但是也可以访问220V接口,违背了最少知道原则
adapter1.outputAC220();
}
}
类适配器角色是通过继承原始类和实现目标接口的方式实现的
对象适配器
同样适用上面这个场景
public class AC220 {
public int outputAC220(){
int output = 220;
System.out.println("输出电压是 "+output +" V");
return output;
}
}
//
public interface DC5 {
//目标接口
int output5V();
}
//适配器角色
public class PowerAdapter implements DC5 {
private AC220 ac220;
public PowerAdapter(AC220 ac220) {
this.ac220 = ac220;
}
@Override
public int output5V() {
int adapterIutput = ac220.outputAC220();
int adapterOutput = adapterIutput / 44 ;
System.out.println("使用适配器输入"+adapterIutput +"V"+",输出"+adapterOutput+"V");
return adapterOutput;
}
}
//测试
public class Test {
public static void main(String[] args) {
PowerAdapter adapter = new PowerAdapter(new AC220());
adapter.output5V();
}
}
解决了类适配器的最少知道原则问题
对象适配器的适配器角色是将原始角色以成员变量的方式传入适配器中的
类适配器和对象适配器的侧重点是系统原始角色转换成目标角色
接口适配器侧重解决接口方法过多,类的臃肿问题,就比如原来的主需要将220V转换成5V,但是现在可能也需要转换成11V、22V、110V等等,类适配和对象适配就要创建很多个类
接口适配器
//
public class AC220 {
public int outputAC220(){
int output = 220;
System.out.println("输出电压是 "+output +" V");
return output;
}
}
public interface DC {
int outPut5V();
int outPut11V();
int outPut22V();
int outPut110V();
}
public class PowerAdapter implements DC {
private AC220 ac220;
public PowerAdapter(AC220 ac220) {
this.ac220 = ac220;
}
@Override
public int outPut5V() {
int adapterIutput = ac220.outputAC220();
int adapterOutput = adapterIutput / 44 ;
System.out.println("使用适配器输入"+adapterIutput +"V"+",输出"+adapterOutput+"V");
return adapterOutput;
}
@Override
public int outPut11V() {
int adapterIutput = ac220.outputAC220();
int adapterOutput = adapterIutput / 20 ;
System.out.println("使用适配器输入"+adapterIutput +"V"+",输出"+adapterOutput+"V");
return adapterOutput;
}
@Override
public int outPut22V() {
int adapterIutput = ac220.outputAC220();
int adapterOutput = adapterIutput / 10 ;
System.out.println("使用适配器输入"+adapterIutput +"V"+",输出"+adapterOutput+"V");
return adapterOutput;
}
@Override
public int outPut110V() {
int adapterIutput = ac220.outputAC220();
int adapterOutput = adapterIutput / 2 ;
System.out.println("使用适配器输入"+adapterIutput +"V"+",输出"+adapterOutput+"V");
return adapterOutput;
}
}
public class Test {
public static void main(String[] args) {
PowerAdapter adapter = new PowerAdapter(new AC220());
adapter.outPut11V();
}
}
适配器模式在业务场景中的应用
场景:登录注册,正常登录注册功能自己系统都有一套用户密码,随着业务发展登录接口还需要对接QQ、WeChat、手机验证吗等登录方式,接口如何设计呢?
//用户密码实体
@Data
public class Member {
private String username;
private String password;
private String mid;
private String info;
}
//统一的接口响应
@Data
public class ResultMsg {
private int code;
private String msg;
private Object data;
public ResultMsg(int code, String msg, Object data) {
this.code = code;
this.msg = msg;
this.data = data;
}
}
//登录注册Service
public class PassportService {
/**
* 注册方法
*/
public ResultMsg regist(String username,String password){
return new ResultMsg(200,"注册成功",new Member());
}
/**
* 登录的方法
*/
public ResultMsg login(String username,String password){
return null;
}
}
//第三方登录接口
public interface IPassportForThird {
ResultMsg loginForQQ(String openId);
ResultMsg loginForWechat(String openId);
ResultMsg loginForToken(String token);
ResultMsg loginForTelphone(String phone, String code);
}
//第三方登录接口实现类
public class PassportForThirdAdapter extends PassportService implements IPassportForThird {
public ResultMsg loginForQQ(String openId) {
System.out.println("QQ登录");
//todo something
return loginForRegist(openId,null);
}
public ResultMsg loginForWechat(String openId) {
System.out.println("微信登录");
//todo something
return loginForRegist(openId,null);
}
public ResultMsg loginForToken(String token) {
System.out.println("Token登录");
//todo something
return loginForRegist(token,null);
}
public ResultMsg loginForTelphone(String phone, String code) {
System.out.println("手机登录");
//todo something
return loginForRegist(phone,null);
}
private ResultMsg loginForRegist(String username,String password){
if(null == password){
password = "THIRD_EMPTY";
}
super.regist(username,password);
return super.login(username,password);
}
}
//测试
public class Test {
public static void main(String[] args) {
PassportForThirdAdapter adapter = new PassportForThirdAdapter();
adapter.login("zhangsan","123456");
adapter.loginForQQ("lkuweyrhsdkjhgssdgsfd");
adapter.loginForWechat("wqerjsdhfghskjdee");
}
}
如果后边再有新的登录对接PassportForThirdAdapter 类中的逻辑就会特别多,特别臃肿
如何去优化呢? 让扩展的时候只需要增加一个类就好了
1.将每种登录方式通过单独一个适配类来实现逻辑
2.想一下哪些逻辑可以通过抽象类抽离出来
优化
// An highlighted block
@Data
public class Member {
private String username;
private String password;
private String mid;
private String info;
}
//登录注册Service
public class PassportService {
/**
* 注册方法
*/
public ResultMsg regist(String username,String password){
return new ResultMsg(200,"注册成功",new Member());
}
/**
* 登录的方法
*/
public ResultMsg login(String username,String password){
return null;
}
}
//第三方登录接口
public interface IPassportForThird {
ResultMsg loginForQQ(String openId);
ResultMsg loginForWechat(String openId);
ResultMsg loginForToken(String token);
ResultMsg loginForTelphone(String phone, String code);
}
//登录适配器接口
public interface ILoginAdapter {
//为了防止用户调用传错适配器的种类,加了一个容错的判断
boolean support(Object object);
//具体的登录接口
ResultMsg login(String id, Object adapter);
}
//将注册方法抽离出来,每种适配器要去继承此类
//本身注册方法要调用PassportService的regist()方法,每种适配器也要实现ILoginAdapter中的接口
public abstract class AbstraceAdapter extends PassportService implements ILoginAdapter {
protected ResultMsg loginForRegist(String username, String password){
if(null == password){
password = "THIRD_EMPTY";
}
super.regist(username,password);
return super.login(username,password);
}
}
//QQ
public class LoginForQQAdapter extends AbstraceAdapter{
public boolean support(Object adapter) {
return adapter instanceof LoginForQQAdapter;
}
public ResultMsg login(String id, Object adapter) {
if(!support(adapter)){return null;}
//accesseToken
//todosomething 获取openId等等逻辑
return super.loginForRegist(id,null);
}
}
//手机
public class LoginForTelAdapter extends AbstraceAdapter{
public boolean support(Object adapter) {
return adapter instanceof LoginForTelAdapter;
}
public ResultMsg login(String id, Object adapter) {
return super.loginForRegist(id,null);
}
}
//微信
public class LoginForWechatAdapter extends AbstraceAdapter{
public boolean support(Object adapter) {
return adapter instanceof LoginForWechatAdapter;
}
public ResultMsg login(String id, Object adapter) {
if(!support(adapter)){return null;}
//todosomething
return super.loginForRegist(id,null);
}
}
//token
public class LoginForTokenAdapter extends AbstraceAdapter {
public boolean support(Object adapter) {
return adapter instanceof LoginForTokenAdapter;
}
public ResultMsg login(String id, Object adapter) {
if(!support(adapter)){return null;}
//todosomething
return super.loginForRegist(id,null);
}
}
//调度类
public class PassportForThirdAdapter implements IPassportForThird {
public ResultMsg loginForQQ(String openId) {
System.out.println("QQ");
return processLogin(openId, LoginForQQAdapter.class);
}
public ResultMsg loginForWechat(String openId) {
System.out.println("Wechat");
return processLogin(openId, LoginForWechatAdapter.class);
}
public ResultMsg loginForToken(String token) {
System.out.println("Token");
return processLogin(token, LoginForTokenAdapter.class);
}
public ResultMsg loginForTelphone(String phone, String code) {
System.out.println("Telphone");
return processLogin(phone, LoginForTelAdapter.class);
}
private ResultMsg processLogin(String id,Class<? extends ILoginAdapter> clazz){
try {
ILoginAdapter adapter = clazz.newInstance();
if (adapter.support(adapter)){
return adapter.login(id,adapter);
}
//todo something
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
查看类图
适配器模式在源码中的体现
Spring AOP中的AdvisorAdapter 有三个实现类 MethodBeforeAdviceAdapter、AfterRuningAdviceAdapter、ThrowsAdviceAdapter
Spring MVC的HandlerAdapter
适配器模式的优点
1.能提高类的透明性和复用,现有的类复用但不需要改变
2.目标类和适配器类解耦,提高程序的扩展性
3.在很多业务场景中符合开闭原则
适配器模式的缺点
1.适配器编写过程需要全面考虑,可能会增加系统的复杂性
2.增加代码阅读难度,降低代码可读性,过多使用适配器会使系统代码变得凌乱
桥接模式的定义
桥接模式也称桥梁模式、接口模式或柄体模式,是将抽象部分与它的具体实现部分分离,使他们都可以独立的变化
通过组合的方式建立两个类之间的联系,而不是继承
属于结构型模式
桥接模式的适用场景
1.在抽象和具体实现之间需要增加更多的灵活性的场景
2.一个类存在 两个或多个独立变化的维度,而这两个或多个维度都需要独立进行扩展
3.不希望使用继承,或因为多层继承导致系统类的个数剧增
桥接模式的常规写法
场景:课程接口(录制课程方法)、笔记接口(记录笔记方法)、视频接口(录制视频方法)
正常
//课程接口
public interface ICourse {
}
//笔记接口
public interface INote {
}
//视频接口
public interface IVideo {
}
//创建java课程、笔记、视频
public class JavaCourse implements ICourse {
}
public class JavaNote implements INote {
}
public class JavaVideo implements IVideo {
}
//创建Python课程、笔记、视频也是一样... 分别实现三个接口
public class PythonCourse extends AbstractCourse {
}
public class PythonNote implements INote {
}
public class PythonVideo implements IVideo {
}
查看类图,可以看出这三个是不同的继承体系,如何将他们关联起来呢
桥接
//课程接口
public interface ICourse {
}
//笔记接口
public interface INote {
}
//视频接口
public interface IVideo {
}
//实现课程接口 持有笔记和视频接口的引用
@Data
public class AbstractCourse implements ICourse {
private INote note;
private IVideo video;
}
//创建java课程
public class JavaCourse extends AbstractCourse {
}
//创建Python课程
public class PythonCourse extends AbstractCourse {
}
查看类图
实际案例演示
场景:发送消息可以通过 邮件、短信两个途径发送,同一种消息有两个紧急程度:普通、加急
两个不同维度如何实现更好的扩展设计呢
//消息接口
public interface IMessage {
//发送消息的内容和接收人
void send(String message, String toUser);
}
//邮件
public class EmailMessage implements IMessage {
public void send(String message, String toUser) {
System.out.println("使用邮件消息发送" + message + "给" + toUser);
}
}
//短信
public class SmsMessage implements IMessage {
public void send(String message, String toUser) {
System.out.println("使用短信消息发送" + message + "给" + toUser);
}
}
//创建抽象类 持有发送接口的引用
public abstract class AbastractMessage {
private IMessage message;
public AbastractMessage(IMessage message) {
this.message = message;
}
void sendMessage(String message,String toUser){
this.message.send(message,toUser);
}
}
//普通消息
public class NomalMessage extends AbastractMessage {
public NomalMessage(IMessage message) {
super(message);
}
}
//加急消息
public class UrgencyMessage extends AbastractMessage {
public UrgencyMessage(IMessage message) {
super(message);
}
void sendMessage(String message, String toUser){
message = "【加急】" + message;
super.sendMessage(message,toUser);
}
}
//测试
public class Test {
public static void main(String[] args) {
IMessage message = new SmsMessage();
AbastractMessage abastractMessage = new NomalMessage(message);
abastractMessage.sendMessage("加班申请","王总");
message = new EmailMessage();
abastractMessage = new UrgencyMessage(message);
abastractMessage.sendMessage("加班申请","王总");
}
}
查看类图
桥接模式在源码中的体现
链接: DriverManager与桥接(Bridge)模式
桥接模式的优点
1.分离抽象部分及其具体实现部分
2.提高了系统的可扩展性
3.符合开闭原则
4.符合合成复用原则
桥接模式的缺点
1.增加了系统的理解与设计难度
2.需要正确的识别系统中两个独立变化的维度