目录
背景
介绍
具体示例
具体实现
优点
Lambda表达式和函数式接口
Lambda
函数式接口
使用函数式接口
行为参数化对常用设计模式的重构
策略模式
普通策略模式
下面使用lambda表达式来重构
另外
模版方法
普通模版方法
下面使用lambda表达式来重构这个模版方法
另外
工厂模式
普通工厂模式
下面使用lambda表达式来重构这个模版方法
另外
结语
背景
在日常开发中,有个众所周知的问题,不管你做什么,用户的需求肯定会变。
比方说,今天的需求是把所有“被打回”状态的合同更新“待跟进”状态,明天的需求又是把所有支付过进场费的合同打上标签。
需要应对不断变化的需求,又需要把工作量降到最小、出错率降到最低,“行为参数化”是可以帮助你处理频繁变更的需求的一种软件开发模式 。
介绍
一言以蔽之,它意味着拿出一个代码块,把它准备好却不去执行它。这个代码块以后可以被你程序的其他部分调用。
例如,你可以将代码块作为参数传递给另一个方法,稍后再去执行它。这样,这个方法的行为就基于那块代码被参数化了。
具体示例
如果我们采用行为参数化这种开发模式,针对以上的需求,我们就可以准备好一个doDataBrushing的方法,参数是需要对所有合同(或其他主体)进行的某个操作,它可以接受不同的新行为作为参数,然后去执行。
具体实现
BrushDataTemplateUtil#doDataBrushing
package com.enmonster.optimon.bos.utils;
import com.enmonster.optimon.bos.common.constant.NumberConstant;
import com.enmonster.optimon.bos.service.brushdata.HandleBrushDataService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.util.concurrent.ExecutorService;
/**
* 刷数据模板工具类
*
* @author lvchengyi
* @datetime 2020/12/11 15:25
*/
@Slf4j
@Component
public class BrushDataTemplateUtil {
/**
* 功能描述:刷数据处理模板
*
* @param startId 起始ID
* @param overId 结束ID
* @param logTag 打印日志TAG
* @param executorService 处理线程池
* @param func 核心处理方法
* @return void
* @author lvchengyi
* @date 15:27 2020/12/11
*/
public void doDataBrushing(long startId, long overId, String logTag, ExecutorService executorService, HandleBrushDataService func) {
long startTime = System.currentTimeMillis();
//最大主键id
final long maxId = overId + 1;
// id区间500
final int limit = NumberConstant.NUMBER_FIVE_HUNDRED;
for (long i = startId; i < maxId; i += limit) {
final long index = i;
executorService.execute(() -> {
try {
log.info("【{}】开始-startId:{}", logTag, index);
long endId = Math.min(maxId, index + limit);
// 数据处理
long count = func.handlePartition(index, endId);
log.info("【{}】处理数据{}个-startId:{}-endId:{}", logTag, count, index, endId);
} catch (Exception e) {
log.error("【{}】发生异常-startId:{}-endId:{}", logTag, index, Math.min(maxId, index + limit), e);
} finally {
log.info("【{}】结束-startId:{},共耗时{}s", logTag, index, (System.currentTimeMillis() - startTime) / 1000);
}
});
}
}
} |
HandleBrushDataService 处理类
package com.enmonster.optimon.bos.service.brushdata;
/**
* 刷数据模板 核心处理方法
*
* @author lvchengyi
* @datetime 2020/12/11 15:18
*/
@FunctionalInterface
public interface HandleBrushDataService {
/**
* 功能描述:处理分片数据
*
* @param partitionStartId 分片开始id
* @param partitionEndId 分片结束id
* @return long 处理数据数
* @author lvchengyi
* @date 15:19 2020/12/11
*/
long handlePartition(long partitionStartId, long partitionEndId);
}
|
实际使用场景
@Override
public void brushHadApportion(Long startId, Long overId) {
brushDataTemplateUtil.doDataBrushing(startId, overId, "处理XXX", dataTransferExecutor, (partitionStartId, partitionEndId) -> {
// 准备数据源
// ... do something ...
// 创建结果容器
// ... do something ...
// 处理逻辑
// ... do something ...
return num;
});
} |
|
优点
1.避免重复的工作,降低出错率
3.代码清晰易于理解,易于长期维护
Lambda表达式和函数式接口
行为参数化即定义代码块为行为,并传递它。Java8提供了Lambda表达式来替代匿名实现类(避免了新建类,代码实现上也更为简洁)。
Lambda
Lambda表达式理解为简洁地表示可传递的匿名函数的一种方式:它没有名称,但它有参数列表、函数主体、返回类型,可能还有一个可以抛出的异常。
Lambda的四大特点:
匿名——我们说匿名,是因为它不像普通的方法那样有一个明确的名称:写得少而想得多!
函数——我们说它是函数,是因为Lambda函数不像方法那样属于某个特定的类。但和方 法一样,Lambda有参数列表、函数主体、返回类型,还可能有可以抛出的异常列表。
传递——Lambda表达式可以作为参数传递给方法或存储在变量中。
简洁——无需像匿名类那样写很多模板代码。
函数式接口
函数式接口就是只定义一个抽象方法的接口。比如日常使用中的:Comparator和Runnable。
函数式接口的抽象方法的签名基本上就是Lambda表达式的签名(函数描述符),是用函数式接口抽象方法的签名来描述需要什么样签名的Lambda表达式。
例如,Runnable接口可以看作一个什么也不接受什么也不返回(void)的函数的签名。因为它只有一个叫作run的抽象方法,这个方法什么也不接受,什么也不返回(void),所以Lambda表达式可以写作 () ->Void。
Lambda表达式可以传递给一个接受函数式接口作为参数的方法,当然这个Lambda表达式的签名要和函数式接口的抽象方法的签名一样。
使用函数式接口
除了我们日常中用到的Comparator、Runnable等,Java8还在java.util.function 引入了新的函数式接口,比如Predicate、Consumer、Function、Supplier 。
Predicate
java.util.function.Predicate<T>接口定义了一个名叫test的抽象方法,它接受泛型 T对象,并返回一个boolean。
Consumer
java.util.function.Consumer<T>接口定义了一个名叫accept的抽象方法,它接受泛型T 的对象,没有返回(void)。
Function
java.util.function.Function<T, R>接口定义了一个叫作apply的方法,它接受一个 泛型T的对象,并返回一个泛型R的对象。
Supplier
java.util.function.Supplier<T>接口定义了一个叫作get的方法,它不接受对象,返回一个泛型T的对象。
常用的函数式接口
函数式接口 | 函数描述符 | 原始类型特化 |
---|
Predicate<T> | T->boolean | IntPredicate、LongPredicate、DoublePredicate |
Consumer<T> | T->void | IntConsumer、LongConsumer、DoubleConsumer |
Function<T,R> | T->R | IntFunction<R>、IntToDoubleFunction、IntToLongFunction LongFunction<R>、LongToDoubleFunction、LongToIntFunction DoubleFunction<R>、ToIntFunction<T>、ToDoubleFunction<T>、ToLongFunction<T> |
Supplier<T> | ()->T | BooleanSupplier、IntSupplier、LongSupplier、DoubleSupplier |
UnaryOperator<T> | T->T | IntUnaryOperator、LongUnaryOperator、DoubleUnaryOpertor |
BinaryOperator<T> | (T,T)->T | IntBinaryOperator、LongBinaryOperator、DoubleBinaryOperator |
BiPredicate<L,R> | (L,R)->boolean | |
BiConsumer<T,U> | (T,U)->void | ObjIntConsumer<T>、ObjLongConsumer<T>、ObjDoubleConsumer<T> |
BiFunction<T,U,R> | (T,U)->R | ToIntBiFunction<T,U>、ToLongBiFunction<T,U>、ToDoubleBiFunction<T,U> |
行为参数化对常用设计模式的重构
策略模式
策略模式定义了一组算法,将它们逐个封装起来,并使它们可以相互替换。
普通策略模式
// 修改单策略模式
interface ModifyContractStrategy {
// 参数校验
boolean paramCheck(ModifyContractSubDTO contractSubDTO, ModifyContractKaFlowReqDTO modifyReqDTO);
}
// 电子修改单策略
public class ElectronicModifyContractStrategyImpl implements ModifyContractStrategy{
@Override
public void paramCheck(ModifyContractSubDTO contractSubDTO, ModifyContractKaFlowReqDTO modifyReqDTO) {
// 具体代码参略
}
}
// 纸质修改单策略
public class PaperModifyContractStrategyImpl implements ModifyContractStrategy{
@Override
public void paramCheck(ModifyContractSubDTO contractSubDTO, ModifyContractKaFlowReqDTO modifyReqDTO) {
// 具体代码参略
}
} |
下面使用lambda表达式来重构
// lambda-电子修改单策略/纸质修改单策略
ModifyContractStrategy strategy = (ModifyContractSubDTO contractSubDTO, ModifyContractKaFlowReqDTO modifyReqDTO) -> {
// 具体代码省略
}
strategy.paramCheck(contractSubDTO, modifyReqDTO); |
另外
适用于逻辑不复杂,策略实现类过多的场景。
甚至有时可以考虑将策略实现写在枚举类中。
模版方法
如果你需要采用某个算法的框架,同时又希望有一定的灵活度,能对它的某些部分进行改进, 那么采用模板方法设计模式是比较通用的方案。
普通模版方法
操作某个单据的抽象处理模版,若需要使用此类模版,需要继承AbstractHandleShopModifyBusiness并重写checkBusiness、handleBill、executeAfterHandle的方法。
public abstract class AbstractHandleShopModifyBusiness<R extends BaseHandleResultDTO, P extends BaseHandleProcessReqDTO>
implements HandleProcessBusiness<R, P> {
/**
* 功能描述:核心流程处理方法
*
* @param reqDTO 入参DTO
* @return R 处理结果DTO
* @author lvchengyi
* @date 17:00 2020/12/1
*/
protected final R handle(P reqDTO) {
// 业务校验
BaseShopModifyCheckDTO checkDTO = checkBusiness(reqDTO);
// 处理业务
R handleResult = handleBill(reqDTO, checkDTO);
// 处理后执行
if (TransactionSynchronizationManager.isActualTransactionActive()) {
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
@Override
public void afterCommit() {
executeAfterHandle(checkDTO, handleResult);
}
});
} else {
executeAfterHandle(checkDTO, handleResult);
}
return handleResult;
}
}
|
下面使用lambda表达式来重构这个模版方法
使用lambda表达式后,如果需要使用模版,不再需要继承抽象类,只需要把对应方法的实现作为参数传入
protected final R handle(P reqDTO,
Function<P, BaseShopModifyCheckDTO> checkBusinessFunction,
BiFunction<P, BaseShopModifyCheckDTO, R> handleBillBiFunction,
BiConsumer<BaseShopModifyCheckDTO, R> executeAfterHandleBiConsumer) {
// 业务校验
BaseShopModifyCheckDTO checkDTO = checkBusinessFunction.apply(reqDTO);
// 处理业务
R handleResult = handleBillBiFunction.apply(reqDTO, checkDTO);
// 处理后执行
if (TransactionSynchronizationManager.isActualTransactionActive()) {
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
@Override
public void afterCommit() {
executeAfterHandleBiConsumer.accept(checkDTO, handleResult);
}
});
} else {
executeAfterHandleBiConsumer.accept(checkDTO, handleResult);
}
return handleResult;
} |
另外
上面的示例模版处理方法有3个,也不是一定要用行为参数化的方式,毕竟参数过多对于一个方法来说也是不太优雅的。
是使用继承方式来使用模版方法还是使用行为参数化的方式来使用,还是根据实际情况来考虑。
工厂模式
工厂方法模式是一种实现了“工厂”概念的面向对象设计模式。就像其他创建型模式一样,它也是处理在不指定对象具体类型的情况下创建对象的问题。
普通工厂模式
public class HandleProcessFactory {
public Shape getProcess(String process){
if(process == null){
return null;
}
if(process.equalsIgnoreCase("AGREE")){
return new Agree();
} else if(process.equalsIgnoreCase("REJECT")){
return new Reject();
}
return null;
}
} |
下面使用lambda表达式来重构这个模版方法
public class HandleProcessFactory {
final static Map<String, Supplier<HandleProcess>> map = new HashMap<>();
static {
map.put("AGREE", Agree::new);
map.put("REJECT", Reject::new);
}
public Shape getProcess(String process){
Supplier<Shape> shape = map.get(process);
if(shape != null) {
return shape.get();
}
return null;
}
} |
另外
实际在项目中,对象的创建一般都依赖Spring,不会使用到该类工厂模式来创建。
结语
可以用1行代码解决的事,绝对不用3行。写的越多出错的可能也就越大。