谈谈模板方法模式在实际生产的应用及代码复用

2022/03/20

谈谈模板方法模式在实际生产的应用及代码复用

所谓模板方法

18 年,仍是个小白的时候写了一篇 谈谈模板方法模式

这篇文章也不想再复制一些难以读懂的概念,简而言之,模板方法就是提取一些公用代码到父类,然后由子类去具体实现父类里没有实现的代码逻辑,目的就是为了代码的复用

在生产里的应用及出现的问题

FTP 消费任务(完美贴合)

这个业务是这样的,由于系统运行在某个不可描述的内网里面,数据交换都是通过 FTP 服务器来进行的,所以当数据发送到 FTP 服务器之后,我们就需要定时拉取上面的文件下来,并自定义文件的业务处理流程,处理成功之后就需要把文件删除掉

所以就有了这么样的一个设计:

abstract class AbstractFtpConsumeJob extends Task {
  public void run() {
    var client = buildFtpClient();
    // 遍历 FTP 上的文件
    // 文件处理
    if (consume(file)) delete(file);
    // ...
  }
  abstract FTPClient buildFtpClient();
  // 其他待子类实现的抽象方法...
}
class ConcreteFtpJob extends AbstractFtpConsumeJob {
  boolean consume(file) {...}
  FTPClient buildFtpClient() {...}
  // 其他抽象方法实现
}

这个设计直至现在用的仍很舒服,但经过了几次改进,主要就是当每次新定义任务时,子类基本上只要实现 consume 方法而已,所以我将 AbstractFtpConsumeJob 的所有抽象方法都替换成了 protected 的实现,这样不仅有了默认的实现,同时也满足子类自定义化的需求

远程接口基类(用得很难受)

同时由于业务的原因,需要从许多接口上爬取数据或者需要传递一些数据到三方接口上,这个设计就是提取公用的 HTTPClient 到基类,然后提供一个发送请求及转换响应的 protected 方法,类似下面:

abstract class BaseRemote {
  protected HTTPClient httpclient;
  protected Resposne execute(Request request){...}
  protected byte[] convertResponse(Response response){...}
}

事实证明,这个设计很失败,由于选择了错误的代码复用方式,结果就导致了这个 BaseRemote 的子类的个性化需求基本上无法在基类上面复用,于是只能一层层继承往上套,一直小修小补

abstract class BaseBussiness1Remote extends BaseRemote {...}
abstract class BaseBussiness2Remote extends BaseBussiness1Remote {...}

说说代码复用

对于有代码洁癖的人,一旦看到重复的代码就恨不得把它们提取出来,使用一个统一入口来管理,我早期也是这样的,但随着系统的演进,这种不加以评估的复用是有问题的

代码复用有风险

看到过许多人接手到一套屎山系统,为了实现一个简单的需求,改了一个小小的地方,需求是完成了,但另外一个看似不相关的地方甚至整个系统都崩了,这其中有一部分原因就是没有评估代码的复用所带来的的影响

所以在复用封装之前一定要评估好,如果一个方法复用另外一个方法之前,还需要看看这个方法的实现而非通过 API 来了解方法的具体作用,那这个封装复用就是失败的

先具体后抽象, 不要过早抽象

抽象也是代码复用的一种,对于上面构建的那些抽象基类,都不是从一开始就设计抽象类,都是从一堆堆极为相似的类中提取出来的

软件工程有句极为经典的话

过早优化是万恶之源

根据我的经验,过早抽象也是万恶之源,有些早期看似一样的代码,随着系统演进发展到后面会变得完全不一样,如果过早把它们抽取到同一个地方,一旦两个位置出现不同的需求,就会出现是改这个公用方法呢,还是改外部方法两难的境地

复用的方式

重构-代码抽取

这种方式的重用范围一般在本类的范围之内

void main() {
  // ...
  // ...
}

void main() {
  f1()
  // ...
}
void f1() {...}

另外一种就是工具类吗,工具类的代码复用就突破了作用域的限制,常见的 xxxUtils, xxxHelper 都是属于工具类

StringUtils.hasLength("cxk");
RedisHelper.set("a", "b");

设计模式

许多设计模式都有代码复用的作用,最明显的当然就是模板方法模式

其他的诸如组合、单例等都有复用的作用

Post Directory