模板方法实战

384 阅读6分钟

模板方法实战

业务场景

开发过程中可能会遇到某两个或多个业务总体流程可以被分成固定的几个部分,其中有一些逻辑是相同的,一些逻辑又不同,尤其是一些状态、名词之类的在各个节点有着明确的区别,但是大体的流程又一致,开发的时候就需要抽离公共代码,一个一个抽离非常的无语。模板方法建议我们将公共逻辑抽离成抽象类的方法,将具体区别的实现写到实现类中,这篇文章就实践下开发中模板方法的使用。

模板方法

image-20220216220501928.png

假设有三个业务都要调用一个方法methodA,methodA的流程是固定的,我们可以对每个节点进行抽象,比如上图就抽象成了4个方法。这三个业务在四个节点的操作都有所不同,按照以往的逻辑,我们需要根据三个业务的某个状态字段在每个节点上用if去做判断,然后对症下药,就是这样

image-20220216220959622.png

甚至坏的情况,可能每个 if 块里面就把一个业务的四个节点的逻辑都编写了。这样的旧代码也可以用 策略 + 工厂 去掉if,但是为什么我要用模板方法呢?

image-20220216221237606.png

模板方法适用于大量重复逻辑和非重复逻辑混合的情况,如果每个流程都用 策略 + 工厂去解耦,业务会更复杂,一个流程用了十几个工厂,业务复杂度不降反增,毕竟使用设计模式还是会冗余部分代码的。如果业务中重复的部分很多,逻辑更有条理,就可以使用模板方法设计模式,同时对多个有区别的操作很好的进行区分,顺带抽离公共代码到抽象类中,一举两得,一箭双雕。当然不是说二选一,一些复杂业务节点还是需要使用 策略 + 工厂去更好的解耦的。

如何抽离公共代码? 模板方法的建议是可以抽离成抽象类,将公共的部分默认实现,将不同部分的实现写在不同的实现类中。

伪代码

业务描述

三个业务 ,三个控制层:

AController、BController、CController

三个业务层接口,三个实现类:

AService、BService、CService

AServiceImpl、BServiceImpl、CServiceImpl

之前的业务逻辑:

AController 调用 AService ---> AServiceImpl

BController 调用 BService ---> BServiceImpl

CController 调用 CService ---> CServiceImpl

旧代码抽象

假设我们已经将ABC三个流程拆分到了三套不同的代码里面,伪代码如下:

// 控制层 A
@RestController
class AController{
    
    @Resource
    private AService aService;
    
    // 调用 服务层 a
    @GetMapping("a")
    public Response<String> getA(String name){
        return Response.ok(aService.getName(name));
    }
}

// 控制层 B
@RestController
class BController{
    
    @Resource
    private BService bService;
    
    // 调用 服务层 b
    @GetMapping("b")
    public Response<String> getB(String name){
        return Response.ok(aService.getName(name));
    }
}

// 控制层 c
@RestController
class CController{
    
    @Resource
    private CService cService;
    
    // 调用 服务层 c
    @GetMapping("c")
    public Response<String> getC(String name){
        return Response.ok(cService.getName(name));
    }
}

用getName方法举例,这个方法可以被分成四个部分:

1.校验 verify()

2.查询数据 getData()

3.比较数据 compareData()

4.更新数据 modifyData()

其中 这三部分是A、B、C业务不同的

✘校验 verify()

✘比较数据 compareData()

✘更新数据 modifyData()

查询数据步骤是相同的

✔查询数据 getData()

// 服务层 a
interface AService{
    String getName(String name);
}

// 服务层实现类 a
@Service
class AServiceImpl implements AService{

    @Override
    public String getName(String name){
        // 校验
        verify();
        // 查询数据
        getData();
        // 比较数据
        compareData();
        // 更新数据
        return modifyData();
    }

    // a 自己的 校验 代码
    public void verify(){
        // verify a
    }

    // a b c 的查询数据逻辑一致
    public void getData(){
        // getData
    }

    // a 自己的 比较数据 代码
    public void compareData(){
        // compareData a
    }

    // a 自己的 更新数据 代码
    public String modifyData(){
        // modify 
        return "a";
    }
}

这是对 A 流程优化之后的代码,单单是将A的 getName 方法抽离成了四个方法。B 和 C的代码同 A 一样,在这个基础上 A、B、C 都抽离出了这四个方法,现在我们要用模板方法把它整合成一套代码。

因为 A、B、C 流程有很多公共部分,我们要做的就是,在业务层抽离出一个抽象类来,按照阿里的规范,抽象类以 Abstract 开头,所以这个抽象类就叫做 AbstractProjectService

新代码抽象

现在的业务逻辑:

AController 调用 AService --->                                        ---> AServiceImpl

BController 调用 BService --->    AbstractProjectService ---> BServiceImpl

CController 调用 CService --->                                        ---> CServiceImpl

Controller

控制层还是三个 AController 、BController 、CController 也都调用各自的 Service

AbstractProjectService

抽象类来编写getName方法,并声明逻辑不同的三个抽象方法和一个默认实现的查询方法

// 抽象类
abstract class AbstractProjectService{

    // 抽象类 抽离主体方法
    public String getName(String name){
        // 校验
        verify();
        // 查询数据
        getData();
        // 比较数据
        compareData();
        // 更新数据
        return modifyData();
    }

    // 抽象校验方法,留给 AServiceImpl BServiceImpl CServiceImpl 各自实现
    protected abstract void verify();

    // 默认实现方法,因为 A B C 的 getData 步骤一致
    protected void getData(){
        // getData
    }

    // 抽象比较数据方法,留给 AServiceImpl BServiceImpl CServiceImpl 各自实现
    protected abstract void compareData();

    // 抽象修改数据方法,留给 AServiceImpl BServiceImpl CServiceImpl 各自实现
    protected abstract String modifyData();
}

AService + AServiceImpl

// 服务层 a
interface AService {
    String getName(String name);
}

// 服务层实现类 a
// 注意这里继承了 AbstractProjectService
@Service
class AServiceImpl extends AbstractProjectService implements AService{

    // a 私有的校验方法
    @Override
    public void verify(){
        // verify a
    }

    // a 私有的比较数据的方法
    @Override
    public void compareData(){
        // compare a
    }

    // a 私有的更新数据的方法
    @Override
    public String modifyData(){
        // modify a
        return "a";
    }
}

BService + BServiceImpl

// 服务层 b
interface BService {
    String getName(String name);
}

// 服务层实现类 b
// 注意这里继承了 AbstractProjectService
@Service
class BServiceImpl extends AbstractProjectService implements BService{

    // b 私有的校验方法
    @Override
    public void verify(){
        // verify b
    }

    // b 私有的比较数据的方法
    @Override
    public void compareData(){
        // compare b
    }

    // b 私有的更新数据的方法
    @Override
    public String modifyData(){
        // modify b
        return "b";
    }
}

CService + CServiceImpl

// 服务层 c
interface CService {
    String getName(String name);
}

// 服务层实现类 c
// 注意这里继承了 AbstractProjectService
@Service
class CServiceImpl extends AbstractProjectService implements CService{

    // c 私有的校验方法
    @Override
    public void verify(){
        // verify c
    }

    // c 私有的比较数据的方法
    @Override
    public void compareData(){
        // compare c
    }

    // c 私有的更新数据的方法
    @Override
    public String modifyData(){
        // modify c
        return "c";
    }
}

结论

伪代码使用模板方法模式,将A、B、C逻辑中的公共部分(比如 getData 方法)在 抽象类中默认实现,将 不同的部分(如 verifycompareDatamodifyData 方法)仅在 抽象类中声明,在 A、B、C各自的业务层实现类中具体实现。由于OOP多态的特性,在A、B、C的控制层调用A、B、C各自的业务层方法,实际上调用的是A、B、C各自业务层实现类中实现的逻辑。

使用模板方法可以很好的降低代码复杂度,提高代码可重用性,可以将业务逻辑梳理的更清晰,但同时也给业务开发提出了更高的要求,要求我们对业务进行更高度的抽象,以便将不同业务的一个逻辑梳理成相同的处理部分,将不同的处理代码由不同的实现类重写,就如同开头举例的 methodA

image-20220216220501928.png

bye, bye ~