文章目录
- 在实际开发过程中,有些时候我们可能会遇到这样的场景:我们定义接口给上游使用,不同的业务类型定义不同的子类型,实现该接口的某个函数,但是这些子类型会有很多公共的逻辑(公共的步骤)。 如果将这部分代码定义为工具方法,就需要在每个子类中都执行对应的调用。 如果有些公共步骤的返回值和接口中定义的返回值一致时,很容易出现漏调用的情况。 那么,该如何 “强制”子类型都要执行一些相同的步骤呢?
- 下面都是伪代码,大家不必纠结于具体细节,理解意思即可。 我们需要提供给上游这样一个接口, type 是指当前服务能够处理的类型,something 代表实际执行的业务功能。 public interface SomeInterface { String type(); ResultDTO something(Param param);} 第一个实现类: public class AImpl implements SomeInterface { @Override public String type() { return "A"; } @Override public ResultDTO something(Param param) { // 特有逻辑 ResultDTO resultDTO = buildPart(param); // 构造公共逻辑所需的参数 OtherParam otherParam = new OtherParam(); // 公有逻辑 return SomeUtils.fillCommon(resultDTO, otherParam); } private ResultDTO buildPart(Param param) { ResultDTO result = new ResultDTO(); // 执行查询 // 塞入特有属性 return result; }} 第二个实现类: public class BImpl implements SomeInterface { @Override public String type() { return "B"; } @Override public ResultDTO something(Param param) { // 特有逻辑 ResultDTO resultDTO = buildPart(param); // 构造公共逻辑所需的参数 OtherParam otherParam = new OtherParam(); // 公有逻辑 return SomeUtils.fillCommon(resultDTO, otherParam); } private ResultDTO buildPart(Param param) { ResultDTO result = new ResultDTO(); // 执行查询 // 塞入特有属性 return result; }} 使用时,构造 Map<String,SomeInterface> type2BeanMap ,然后根据当前的 type 去执行具体的实现 Bean。 具体可参考《巧用 Spring 自动注入实现策略模式升级版》 问题:如果我们新增 CImpl 继承 SomeInterface 就必须查看 AImpl 或者 BImpl 源码才知道有一段公共逻辑,很容易遗漏这一段公共逻辑。 如果我们想让新建子类时,不需要担心遗漏这段公共的逻辑,该怎么办?
- 如果大家对设计模式比较熟悉的话,这种场景我们很自然地会联想到模板模式。 我们将采用这种设计模式,对代码进行改造。 (1)我们将特有逻辑上提到接口中,在 default 方法中编排逻辑即可。 public interface SomeInterface { String type(); // 目标方法 default ResultDTO something(Param param) { return SomeUtils.fillCommon(buildPart(param)); } /** * 特有逻辑 */ MiddleParam buildPart(Param param);} 定义为接口的好处是,不会影响到子类继承其他父类型(Java 是单继承机制)。 (2)可以将 SomeInterface 改为抽象类(AbstractSomeService),something定义为 public ,将 builPart 定义为抽象函数,让子类去重写。 public abstract class AbstractSomeService { abstract String type(); // 目标方法 public ResultDTO something(Param param) { return SomeUtils.fillCommon(buildPart(param)); } /** * 特有逻辑 */ abstract MiddleParam buildPart(Param param);} 定义为抽象类的坏处是子类型无法再继承其他类,但理论上也不应该(不需要) 再继承其他类,好处是可以将buildPart 重写的访问修饰符范围降低,如改为 protected。 注意: (1) 本案例里的 SomeUtils.fillCommon 只是伪代码,公共逻辑可能封装在工具类中,也可能封装在某个 bean 中,在抽象类或者接口中可以通过ApplicationContextHolder 去获取并调用。 (2) 实际编码时,公共逻辑也未必在最后调用。 (3) 实际编码中,公共的步骤可能不止一个,但是方案是一致的,有几个定义几个抽象方法即可。 (4)实际编码时,公共的步骤可能不需要返回值 定义中间参数: @Datapublic class MiddleParam { private ResultDTO semiResult; private OtherParam otherParam;} 第一个实现类: public class AImpl extends AbstractSomeService { @Override public String type() { return "A"; } @Override protected MiddleParam buildPart(Param param) { MiddleParam middleParam = new MiddleParam(); // 执行查询 // 塞入特有属性 return middleParam; }} 第二个实现类: public class BImpl extends AbstractSomeService { @Override public String type() { return "B"; } @Override protected MiddleParam buildPart(Param param) { MiddleParam middleParam = new MiddleParam(); // 执行查询 // 塞入特有属性 return middleParam; }} 这样通过类似 buildPart 这种函数名,可以明确感知到当前是对部分逻辑进行处理,且不需要在当前子类中执行公共逻辑的调用。
- 本文案例比较简单,主要思想是使用模板模式来实现公共步骤的编排。 希望大家遇到类似场景时,可以使用更优雅的方式,更健壮的方式去实现,而不是依靠“口口相传” 或者让别人看你代码才知道该怎么写。 创作不易,如果本文对你有帮助,欢迎点赞、收藏加关注,你的支持和鼓励,是我创作的最大动力。
在实际开发过程中,有些时候我们可能会遇到这样的场景:我们定义接口给上游使用,不同的业务类型定义不同的子类型,实现该接口的某个函数,但是这些子类型会有很多公共的逻辑(公共的步骤)。
如果将这部分代码定义为工具方法,就需要在每个子类中都执行对应的调用。
如果有些公共步骤的返回值和接口中定义的返回值一致时,很容易出现漏调用的情况。
那么,该如何 “强制”子类型都要执行一些相同的步骤呢?

下面都是伪代码,大家不必纠结于具体细节,理解意思即可。
下面都是伪代码,大家不必纠结于具体细节,理解意思即可。
我们需要提供给上游这样一个接口, type 是指当前服务能够处理的类型,something 代表实际执行的业务功能。
public interface SomeInterface {
String type();
ResultDTO something(Param param);
}
第一个实现类:
public class AImpl implements SomeInterface {
@Override
public String type() {
return "A";
}
@Override
public ResultDTO something(Param param) {
// 特有逻辑
ResultDTO resultDTO = buildPart(param);
// 构造公共逻辑所需的参数
OtherParam otherParam = new OtherParam();
// 公有逻辑
return SomeUtils.fillCommon(resultDTO, otherParam);
}
private ResultDTO buildPart(Param param) {
ResultDTO result = new ResultDTO();
// 执行查询
// 塞入特有属性
return result;
}
}
第二个实现类:
public class BImpl implements SomeInterface {
@Override
public String type() {
return "B";
}
@Override
public ResultDTO something(Param param) {
// 特有逻辑
ResultDTO resultDTO = buildPart(param);
// 构造公共逻辑所需的参数
OtherParam otherParam = new OtherParam();
// 公有逻辑
return SomeUtils.fillCommon(resultDTO, otherParam);
}
private ResultDTO buildPart(Param param) {
ResultDTO result = new ResultDTO();
// 执行查询
// 塞入特有属性
return result;
}
}
使用时,构造 Map<String,SomeInterface> type2BeanMap ,然后根据当前的 type 去执行具体的实现 Bean。
具体可参考《巧用 Spring 自动注入实现策略模式升级版》
问题:如果我们新增 CImpl 继承 SomeInterface 就必须查看 AImpl 或者 BImpl 源码才知道有一段公共逻辑,很容易遗漏这一段公共逻辑。
如果我们想让新建子类时,不需要担心遗漏这段公共的逻辑,该怎么办?
如果大家对设计模式比较熟悉的话,这种场景我们很自然地会联想到模板模式。
我们将采用这种设计模式,对代码进行改造。
(1)我们将特有逻辑上提到接口中,在 default 方法中编排逻辑即可。
public interface SomeInterface {
String type();
// 目标方法
default ResultDTO something(Param param) {
return SomeUtils.fillCommon(buildPart(param));
}
/**
* 特有逻辑
*/
MiddleParam buildPart(Param param);
}
定义为接口的好处是,不会影响到子类继承其他父类型(Java 是单继承机制)。
(2)可以将 SomeInterface 改为抽象类(AbstractSomeService),something定义为 public ,将 builPart 定义为抽象函数,让子类去重写。
public abstract class AbstractSomeService {
abstract String type();
// 目标方法
public ResultDTO something(Param param) {
return SomeUtils.fillCommon(buildPart(param));
}
/**
* 特有逻辑
*/
abstract MiddleParam buildPart(Param param);
}
定义为抽象类的坏处是子类型无法再继承其他类,但理论上也不应该(不需要) 再继承其他类,好处是可以将buildPart 重写的访问修饰符范围降低,如改为 protected。
注意:
(1) 本案例里的 SomeUtils.fillCommon 只是伪代码,公共逻辑可能封装在工具类中,也可能封装在某个 bean 中,在抽象类或者接口中可以通过ApplicationContextHolder 去获取并调用。
(2) 实际编码时,公共逻辑也未必在最后调用。
(3) 实际编码中,公共的步骤可能不止一个,但是方案是一致的,有几个定义几个抽象方法即可。
(4)实际编码时,公共的步骤可能不需要返回值
定义中间参数:
@Data
public class MiddleParam {
private ResultDTO semiResult;
private OtherParam otherParam;
}
第一个实现类:
public class AImpl extends AbstractSomeService {
@Override
public String type() {
return "A";
}
@Override
protected MiddleParam buildPart(Param param) {
MiddleParam middleParam = new MiddleParam();
// 执行查询
// 塞入特有属性
return middleParam;
}
}
第二个实现类:
public class BImpl extends AbstractSomeService {
@Override
public String type() {
return "B";
}
@Override
protected MiddleParam buildPart(Param param) {
MiddleParam middleParam = new MiddleParam();
// 执行查询
// 塞入特有属性
return middleParam;
}
}
这样通过类似 buildPart 这种函数名,可以明确感知到当前是对部分逻辑进行处理,且不需要在当前子类中执行公共逻辑的调用。
本文案例比较简单,主要思想是使用模板模式来实现公共步骤的编排。
希望大家遇到类似场景时,可以使用更优雅的方式,更健壮的方式去实现,而不是依靠“口口相传” 或者让别人看你代码才知道该怎么写。
创作不易,如果本文对你有帮助,欢迎点赞、收藏加关注,你的支持和鼓励,是我创作的最大动力。
