youyichannel

志于道,据于德,依于仁,游于艺!

0%

桥接模式

桥接模式(桥梁模式),作用:将抽象和实现解耦 => 将抽象部分和实现部分分离

适用场景:扩展第三方登录服务。

简介

桥接模式通用类图:

上图可以分为左右两个部分:

  • 左侧:一个抽象类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);
}

看到这里,你可能会觉得这个AbstractRegisterLoginComponentRegisterLoginFuncInterface类实在是太像了,完全没有存在的必要了,可以删了,不需要创建。

但其实不是这样的,抽象和实现的分离、解耦的过程是需要付出成本的。我们使用「抽象和实现」两种类结构的设计换取了代码的高扩展性,换取了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 {

/**
* Bridge
*/
protected RegisterLoginFuncInterface funcInterface;

public AbstractRegisterLoginComponent(RegisterLoginFuncInterface funcInterface) {
this.funcInterface = funcInterface;
}

// ...
}

抽象子类:调用实现类的方法

public class RegisterLoginComponent extends AbstractRegisterLoginComponent {

/**
* 构造器传入桥梁
*
* @param funcInterface Bridge
*/
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进行覆写,这是一段无用的代码
  • RegisterLoginByDefaultRegisterLoginByGitee都实现了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();
}
// ...
}

AbstarctListadd()方法进行第一次覆写,并抛出了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 {
// 不需要实现login3rd()方法了
}

优化2 —— 冗余代码复用

在优化一中就已经提及了抽象层是解决瑕疵的关键,它可以为子类提供代码的复用逻辑。

那么根据login3rd()的实现逻辑来看,register()login()checkUserExists()都是需要服用的方法。然而这些方法都需要使用持久层UserRepository,如果将这三部分代码的实现直接复制到抽象层,那么会出现一个致命的问题:抽象层不能通过@Autowire等方法注入UserRepository依赖,即使使用@Component修饰抽象类也不能够注入持久层依赖。

因此我们需要从可以注入UserRepository依赖的地方(RegisterLoginByDefaultRegisterLoginByGitee)通过方法参数传递的方式,将持久层依赖传递给抽象层。

优化后的抽象层:

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可以是接口,也可以是抽象类,为什么?什么情况下选择接口?什么情况下选择抽象类?什么情况下选择抽象类作为中间层,选择接口作为顶层?

  1. 如果所有的子类需要实现”父类“的所有方法,不会出现无用的垃圾代码 => 选择接口
  2. 如果所有的子类对”父类“的方法有选择性地实现,并且需要有公共的代码逻辑 => 选择抽象
  3. 基于第二点,在选择使用抽象类的基础上,如果将来有可能扩展一些 必须要所有子类实现的新方法,就需要引入顶层接口

这也能回答上述提出的问题了。