桥接模式(桥梁模式),作用:将抽象和实现解耦 =>
将抽象部分和实现部分分离
适用场景:扩展第三方登录服务。
简介
桥接模式通用类图:
上图可以分为左右两个部分:
- 左侧:一个抽象类
Abstraction
和它的子类RefinedAbstraction
- 右侧:一个接口(也可以是抽象类)
Implementor
和它的子类ConcreteImplementor
中间有一座桥梁,连接左右两部分。
接下来就分左右顺序开始介绍。
实现
Implementor
核心方法的承载接口(或抽象类)
这个词翻译过来是“实现化角色”,就是核心方法的承载角色、核心逻辑的承载角色。
=> 想要实现的功能都要交给Implementor
此处我们需要实现的功能是:
- 第三方账号登录
- 维持已有的用户名 / 密码登录 / 注册功能
那么,首先定义接口(或者抽象类)RegisterLoginFuncInterface
public interface RegisterLoginFuncInterface { String login(String account, String password); String register(UserInfo userInfo); boolean checkUserExists(String username); String login3rd(HttpServletRequest request); }
|
ConcreteImplementor
承载核心方法的具体子类
上一步创建RegisterLoginFuncInterface
接口,那么创建具体子类实现类成为了必不可少的一步,此处我们可以根据需求创建具体的子类:
RegisterLoginByDefault
:支持已有的用户名 /
密码登录
RegisterLoginByGitee
:支持Gitee平台的第三方账号登录功能
- ……
需要注意的是,这部分是承载核心逻辑的代码,必然会和数据库打交道,因此还需要DAO层
@Component public class RegisterLoginByDefault extends AbstractRegisterLoginFunc implements RegisterLoginFuncInterface {
@Autowired private UserRepository userRepository;
@Override public String login(String account, String password) { return null; }
@Override public String register(UserInfo userInfo) { return null; }
@Override public boolean checkUserExists(String username) { return null(); } @Override public String login3rd(HttpServletRequest request) { return null; }
}
@Component public class RegisterLoginByGitee extends AbstractRegisterLoginFunc implements RegisterLoginFuncInterface {
@Autowired private UserRepository userRepository;
@Override public String login(String account, String password) { return null; }
@Override public String register(UserInfo userInfo) { return null; }
@Override public boolean checkUserExists(String username) { return null(); } @Override public String login3rd(HttpServletRequest request) { return null; }
}
|
写出来之后就会发现上述代码是存在缺陷的:
RegisterLoginByDefault
类需要实现login3rd()
方法吗?
RegisterLoginByGitee
类需要实现login()
和register()
方法吗?
答案是都不需要,这个问题我们先按下不表,后面再来优化。
Abstraction
抽象角色,为调用端提供方法调用入口
回顾下上述的UML图,可以发现Abstraction
类是直接暴露给Client端使用的。Clien端只需要知道Abstraction
类就可以了,对于Implementor
类,Client端一无所知,这也符合“最少知识原则”。
再次回顾桥接模式的作用:将抽象和实现解耦。此处来看,“抽象”在左侧,暴露给Client端;“实现”在右侧,对Client端来说完全透明
=> 解耦。
public abstract class AbstractRegisterLoginComponent { public abstract String login(String account, String password); public abstract String register(UserInfo userInfo); public abstract String login3rd(HttpServletRequest request); }
|
看到这里,你可能会觉得这个AbstractRegisterLoginComponent
和RegisterLoginFuncInterface
类实在是太像了,完全没有存在的必要了,可以删了,不需要创建。
但其实不是这样的,抽象和实现的分离、解耦的过程是需要付出成本的。我们使用「抽象和实现」两种类结构的设计换取了代码的高扩展性,换取了Client端的“最少知识原则”,换取了耦合度的降低。
RefinedAbstraction
抽象角色子类
既然抽象角色是为Client调用端提供方法入口的,不可能使用刚刚创建的抽象类的。因此需要创建一个具体的子类,这个子类提供更加细节的方法逻辑。
public class RegisterLoginComponent extends AbstractRegisterLoginComponent {
@Override public String login(String account, String password) { return null; }
@Override public String register(UserInfo userInfo) { return null; }
@Override public String login3rd(HttpServletRequest request) { return null; } }
|
回顾之前所说的,「核心逻辑的实现是在Implementor
中的」,因此,在抽象子类RegisterLoginComponent
中,仅仅需要调用Implementor
中的相同意义的方法就可以了。
怎么调用了?这就需要搭建桥梁了。
搭建桥梁
在抽象类和实现类之间搭建”桥梁“ —— 桥接模式的核心体现
上述的抽象类和实现类之间没有关联,而我们知道,抽象类是需要调用实现类中相同意义的方法即可。
怎么搭建?
通过在抽象类AbstractRegisterLoginComponent
中关联RegisterLoginFuncInterface
接口,并以「构造器」的方式初始化RegisterLoginFuncInterface
接口属性,完成抽象和实现的桥梁搭建。
抽象类:
public abstract class AbstractRegisterLoginComponent {
protected RegisterLoginFuncInterface funcInterface;
public AbstractRegisterLoginComponent(RegisterLoginFuncInterface funcInterface) { this.funcInterface = funcInterface; }
}
|
抽象子类:调用实现类的方法
public class RegisterLoginComponent extends AbstractRegisterLoginComponent {
public RegisterLoginComponent(RegisterLoginFuncInterface funcInterface) { super(funcInterface); } @Override public String login(String account, String password) { return funcInterface.login(account, password); }
@Override public String register(UserInfo userInfo) { return funcInterface.register(userInfo); } @Override public String login3rd(HttpServletRequest request) { return funcInterface.login3rd(request); } }
|
至此,我们已经完整的呈现了「桥接模式」的类结构和方法定义了。
最终的类图:
优化
上述提及的两个瑕疵:
RegisterLoginByDefault
类不需要实现login3rd()
方法,但是由于实现了RegisterLoginFuncInterface
接口,因此必须要对login3rd
进行覆写,这是一段无用的代码
RegisterLoginByDefault
和RegisterLoginByGitee
都实现了login()
和register()
方法,并且代码逻辑完全一致,这是一段冗余的代码
优化1 —— 避免垃圾代码的产生
此处可以效仿JDK的ArrayList
的实现,ArrayList
继承了AbstractList
抽象类并且实现了List
接口,同时AbstractList
抽象类也实现了List
接口,这样设计就是为了打破子类必须实现接口中声明的方法这一强制性规则。
public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E> {
public boolean add(E e) { add(size(), e); return true; }
public void add(int index, E element) { throw new UnsupportedOperationException(); } }
|
AbstarctList
对add()
方法进行第一次覆写,并抛出了UnsupportedOperationException
异常,这么做的好处:
- 不强制子类实现接口中的方法
- 对于不实现
add()
的子类,比如EmptyList
,调用add()
方法就会直接抛出异常
- 类结构层次更有条理
List
接口作为顶层父接口,负责声明所有的相关方法
AbstractList
作为中间层的抽象类,为子类提供了更多的选择性;还可以将公用的代码逻辑存在抽象层(抽象层是解决两个瑕疵的关键)
- 子类可以自由选择需要实现的方法,并且可以随时添加自己独有的方法
那么我们可以效仿之:
public abstract class AbstractRegisterLoginFunc implements RegisterLoginFuncInterface {
@Override public String login(String account, String password) { throw new UnsupportedOperationException(); }
@Override public String register(UserInfo userInfo) { throw new UnsupportedOperationException(); }
@Override public boolean checkUserExists(String username) { throw new UnsupportedOperationException(); }
@Override public String login3rd(HttpServletRequest request) { throw new UnsupportedOperationException(); } }
|
@Component public class RegisterLoginByDefault extends AbstractRegisterLoginFunc implements RegisterLoginFuncInterface { }
|
优化2 —— 冗余代码复用
在优化一中就已经提及了抽象层是解决瑕疵的关键,它可以为子类提供代码的复用逻辑。
那么根据login3rd()
的实现逻辑来看,register()
、login()
、checkUserExists()
都是需要服用的方法。然而这些方法都需要使用持久层UserRepository
,如果将这三部分代码的实现直接复制到抽象层,那么会出现一个致命的问题:抽象层不能通过@Autowire
等方法注入UserRepository
依赖,即使使用@Component
修饰抽象类也不能够注入持久层依赖。
因此我们需要从可以注入UserRepository
依赖的地方(RegisterLoginByDefault
和RegisterLoginByGitee
)通过方法参数传递的方式,将持久层依赖传递给抽象层。
优化后的抽象层:
public abstract class AbstractRegisterLoginFunc implements RegisterLoginFuncInterface {
@Override public String login(String account, String password) { throw new UnsupportedOperationException(); } @Override public String register(UserInfo userInfo) { throw new UnsupportedOperationException(); } @Override public boolean checkUserExists(String username) { throw new UnsupportedOperationException(); } @Override public String login3rd(HttpServletRequest request) { throw new UnsupportedOperationException(); }
protected String commonLogin(String account, String password, UserRepository userRepository) { return "Login Success"; } protected String commonRegister(UserInfo userInfo, UserRepository userRepository) { return "Register success!"; } protected boolean commonCheckUserExists(String username, UserRepository userRepository) { UserInfo user = userRepository.findByUsername(username); return user != null; } }
|
修改后的RegisterLoginByDefault
@Component public class RegisterLoginByDefault extends AbstractRegisterLoginFunc implements RegisterLoginFuncInterface {
@Autowired private UserRepository userRepository; @Override public String login(String account, String password) { return super.commonLogin(account, password, userRepository); }
@Override public String register(UserInfo userInfo) { return super.commonRegister(userInfo, userRepository); }
@Override public boolean checkUserExists(String username) { return super.commonCheckUserExists(username, userRepository); }
}
|
RegisterLoginByGitee
类同理。
问题
最后还有一个问题:可不可以把接口RegisterLoginFuncInterface
删除,直接使用AbstractRegisterLoginFunc
抽象类作为最顶级的父类?
要回答这个问题,需要想清楚之前提及的Implementor
可以是接口,也可以是抽象类,为什么?什么情况下选择接口?什么情况下选择抽象类?什么情况下选择抽象类作为中间层,选择接口作为顶层?
- 如果所有的子类需要实现”父类“的所有方法,不会出现无用的垃圾代码 =>
选择接口
- 如果所有的子类对”父类“的方法有选择性地实现,并且需要有公共的代码逻辑
=> 选择抽象
- 基于第二点,在选择使用抽象类的基础上,如果将来有可能扩展一些
必须要所有子类实现的新方法,就需要引入顶层接口
这也能回答上述提出的问题了。