【进阶之路】理解结构型模式开发(桥接模式)

1,093 阅读7分钟

导言

大家好,我是南橘,从接触java到现在也有差不多两年时间了,两年时间,从一名连java有几种数据结构都不懂超级小白,到现在懂了一点点的进阶小白,学到了不少的东西。知识越分享越值钱,我这段时间总结(包括从别的大佬那边学习,引用)了一些平常学习和面试中的重点(自我认为),希望给大家带来一些帮助

结构型模式主要描述如何将类或对象通过组合新来实现功能。它主要分为类结构型模式对象结构型模式类结构型模式采用继承机制来组织接口和类,对象结构型模式釆用组合或聚合来组合对象。

首先回到开头,我们为什么要使用设计模式?很好理解,设计模式就是前人经验的结晶,每一种设计模式都解决了特定的问题,使用这些设计模式就是站在了前人的肩膀上。例如:在面向对象中,单例模式可以有效的解决一个类的重复实例化问题。对于系统中的某些类来说,只有一个实例很重要。同时,设计模式也很好的增加了代码的复用性,可维护性和可读性(可读性真是大坑啊。我最讨厌两件事,第一件是同事不写备注、第二件是给代码写备注)。

结构型模式分为以下 7 种:

代理(Proxy)模式:为某对象提供一种代理以控制对该对象的访问。即客户端通过代理间接地访问该对象,从而限制、增强或修改该对象的一些特性。
适配器(Adapter)模式:将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类能一起工作。
桥接(Bridge)模式:将抽象与实现分离,使它们可以独立变化。它是用组合关系代替继承关系来实现的,从而降低了抽象和实现这两个可变维度的耦合度。
装饰(Decorator)模式:动态地给对象增加一些职责,即增加其额外的功能。
外观(Facade)模式:为多个复杂的子系统提供一个一致的接口,使这些子系统更加容易被访问。
享元(Flyweight)模式:运用共享技术来有效地支持大量细粒度对象的复用。
组合(Composite)模式:将对象组合成树状层次结构,使用户对单个对象和组合对象具有一致的访问性。

桥接模式

因为其实代理模式和适配器模式大家都已经很懂了,平常遇到的也很多,所以这次我特别从桥接模式来和大家分享,同时我正在做的工作也用到了这个模式。

1、定义

分离抽象与现实,使用组合的关系来代替继承的关系,由于组合关系比继承关系耦合度低,满足“合成复用原则”,所以具有更大的灵活性。

  • 优点
    • 由于抽象与实现分离,所以扩展能力强
    • 实现细节更透明
    • 低耦合度、可以随意更改具体实现类与抽象拓展类的内容
  • 缺点
    • 可读性低,特别是第一次接触的人需要更多时间去理解,但是理解了反而会让人不自觉地减少备注(会了就很简单了)

2、实现

桥接(Bridge)模式包含以下主要角色。

抽象化角色:定义抽象类,并包含一个对实现化对象的引用。
扩展抽象化角色:是抽象化角色的子类,实现父类中的业务方法,并通过组合关系调用实现化角色中的业务方法。
实现化角色:定义实现化角色的接口,供扩展抽象化角色调用。
具体实现化角色:给出实现化角色接口的具体实现。

抽象化角色与扩展抽象化角色 实现化角色与具体实现化角色

这边从代码的继承图可以看出来,抽象化与实现化的角色在结构上并没有偶尔,不存在继承和实现的关系,而是通过是实现化角色与扩展抽象化角色调用。

抽象化角色

这里的代码就是我的一个抽象化角色,它主要在抽象中定义了整个方法的流程,在扩展抽象化角色中可以对其进行继承与重写,并通过组合关系调用实现化角色中的业务方法。

@Slf4j
public abstract class CBHBCallAbstractTianJinComponent implements CBHBCallComponent {
  /**
     * 请求处理
     *
     * @param obj
     * @return
     */
    @Override
    public Object dealData(Object obj) throws ServiceException {
        String id = "";
        RequestBo requ;
        ResponseBo response = null;
        try {
            //1、参数校验
            id = this.checkParam(obj);
            log.info("cbhb checkParam id :{}, obj :{}", id, obj);
            // 2、封装参数
            requ = this.buildParam(obj);
            log.info("cbhb buildParam id :{}, requestDto :{}", id, requ);
            //3、二次校验
            String checkResult = CheckFieldUtil.checkParam(requ);
            if (!CheckFieldUtil.SUCCESS.equals(checkResult)) {
                throw new BizException("参数校验失败");
            }
            // 4、发送请求(调用或者别的)
            response = (ResponseBo) this.doSend(requ, id);
        } catch (BizException e) {
            log.error("cbhb xxxx userId:{} error :{}", id, e.getMessage());
            this.dealCheckFail(e.getMessage(), id);
            throw e;
        } catch (Exception e1) {
            log.error(getMethodDesc(), id, e1.getMessage(), e1);
            this.dealCheckFail(e1.getMessage(), id);
            throw new RuntimeException("userId:" + id + "," + this.getFundLogDesc() + "出错" + e1.getMessage(), e1);
        }

        // 处理结果
        if (this.dealResult(response, id)) {
            return this.dealSuccess(response, id);
        } else {
            return this.dealFail(response, id);
        }
    }
    
	 /**
     * 对传入参数进行校验
     * @param obj
     * @return
     */
    public String checkParam(Object obj) {
        if (Objects.isNull(obj)) {
            throw new BizException("参数为空");
        }
        return "userId";
    }

    /**
     * 申请提交的 校验失败 场景
     *
     * @param content
     * @param userid
     */
    public void dealCheckFail(String content, String userid) {
        log.error(this.getMethodDesc(), userid, content);
        throw new ServiceException(content);
    }

    /**
     * 封装参数
     *
     * @param obj
     * @return
     */
    public abstract RequestBo buildParam(Object obj);

    /**
     * 发送请求
     *
     * @param requ
     * @return
     */
    public abstract ResponseBo doSend(RequestBo requ, String userId);

    /**
     * 结果处理
     *
     * @param resp
     * @return
     */
    public boolean dealResult(ResponseBo resp, String userid) {
        log.info("cbhb request ReqJnlNo {}, desc {}", resp.getReqJnlNo(), resp.getResMsg());
       ....
    }

    /**
     * 处理成功
     *
     * @param resp
     * @return
     */
    public Object dealSuccess(ResponseBo resp, String userid) {
        log.info(getMethodDesc(), userid, "success");
        return resp;
    }

    /**
     * 处理失败
     *
     * @param resp
     * @return
     */
    public Object dealFail(ResponseBo resp, String userid) {
        log.warn(getMethodDesc(), userid, resp.getResCode());
        throw new ServiceException(resp.getResMsg());
    }

    /**
     * 日志描述
     *
     * @return
     */
    public String getMethodDesc() {
        return "xxxx request userId {}, desc {}";
    }

    /**
     * 系统日志 标题
     *
     * @return
     */
    public String getFundLogDesc() {
        return "xxxx";
    }


    

扩展抽象化角色

基本实现父类的方法,具体方法具体分析,可以无限制地进行扩展,比如我这边这些类都对父类方法进行了扩展。 (正在运行与将要运行的代码还是需要保密,只会分享自己自定义的代码)

具体实现化角色

就是很正常的service中的方法

    public boolean loan(LoanBo loanBo) {
        CreditDto creditDto = creditService.findByUserId(loanBo.getUserId());
        loanBo.setApplId(creditDto.getApplId());
        try {
            cbhbLoanComponent.dealData(loanBo);
        } catch (ServiceException e) {
            throw e;
        }
        return true;
    }

实现化角色

  /**
     * 支用 申请
     * @param loanBo
     * @return
     */
    boolean loan(LoanBo loanBo);

我这个例子就很好的展示了桥接模式,如果我需要拓展新功能,只需要拓展实现化角色、具体实现化角色和扩展抽象化角色,新增功能并不会影响之前功能的实现,甚至你可以完全复用抽象化角色的内容。 桥接模式也很好的实现了开闭原则里式替换原则依赖导致原则合成复用原则。但是因为接口太多加上我的懒惰,导致代码功违反了单一职责原则接口隔离原则(接口臃肿)。

3、桥接模式适用于场景

  • 当一个类存在两个独立变化的维度,且这两个维度都需要进行扩展时。
  • 当一个系统不希望使用继承或因为多层次继承导致系统类的个数急剧增加时。
  • 当一个系统需要在构件的抽象化角色和具体化角色之间增加更多的灵活性时。

桥接模式可以理解为一座桥,桥左边为A,桥右边为B,A有A1,A2,A3等,表示桥左边的三个不同地方,B有B1,B2,B3等,表示桥右边的三个不同地方,假设我们要从桥左侧A出发到桥的右侧B,我们可以有多重方案,A1到B1,A1到B2,A1到B3,A2到B1等等。通过桥接模式,我们可以自由的扩展AB。

结语

好久没有写文章了,因为忙(懒),这段时间总结了一些开发经验,继续开始和大家分享~ 有需要的同学可以加我的公众号,以后的最新的文章第一时间都在里面,也可以找我要思维导图 同时需要思维导图的话,可以联系我,毕竟知识越分享越香!