行为参数化的开发模式在日常工作中的使用-Lambda表达式和函数式接口

 

目录

背景

介绍

具体示例

具体实现

优点

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->booleanIntPredicate、LongPredicate、DoublePredicate
Consumer<T>T->voidIntConsumer、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>()->TBooleanSupplier、IntSupplier、LongSupplier、DoubleSupplier
UnaryOperator<T>T->TIntUnaryOperator、LongUnaryOperator、DoubleUnaryOpertor
BinaryOperator<T>(T,T)->TIntBinaryOperator、LongBinaryOperator、DoubleBinaryOperator
BiPredicate<L,R>(L,R)->boolean 
BiConsumer<T,U>(T,U)->voidObjIntConsumer<T>、ObjLongConsumer<T>、ObjDoubleConsumer<T>
BiFunction<T,U,R>(T,U)->RToIntBiFunction<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行。写的越多出错的可能也就越大。

 

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值