当前位置 > it书童 > java > 正文

筛选购物车商品-引出函数式接口妙用

java it书童 2021-01-14 15:24:13 0赞 0踩 183阅读 0评论

购物车初始化

双11购物节,程序员小马在购物车加入一系列商品

商品种类枚举值:

package lambda.cart;

// 商品类型枚举
public enum SkuCategoryEnum {
    CLOTHING(10, "服装类"),
    ELECTRONICS(20, "数码类"),
    SPORTS(30, "运动类"),
    BOOKS(40, "图书类");
    // 商品类型编号
    private Integer code;
    // 商品类型名称
    private String name;

    SkuCategoryEnum(Integer code, String name) {
        this.code = code;
        this.name = name;
    }
}

商品 sku

package lambda.cart;

import lombok.Data;

/**
 * 下单商品信息对象
 */
@Data
public class Sku {
    // 编号
    private Integer skuId;
    // 商品名称
    private String skuName;
    // 单价
    private Double skuPrice;
    // 购买个数
    private Integer totalNum;
    // 总价
    private Double totalPrice;
    // 商品类型
    private Enum skuCategory;

    // 构造函数
    public Sku(Integer skuId, String skuName, Double skuPrice, Integer totalNum, Double totalPrice, Enum skuCategory) {
        this.skuId = skuId;
        this.skuName = skuName;
        this.skuPrice = skuPrice;
        this.totalNum = totalNum;
        this.totalPrice = totalPrice;
        this.skuCategory = skuCategory;
    }
}

购物车中的商品

package lambda.cart;

import java.util.ArrayList;
import java.util.List;

// 购物车服务类
public class CartService {
    // 加入到购物车中的商品信息
    private static List<Sku> cartSkuList = new ArrayList<Sku>(){
        {
            add(new Sku(654032, "无人机",
                4999.00, 1,
                4999.00, SkuCategoryEnum.ELECTRONICS));

            add(new Sku(642934, "VR一体机",
                2299.00, 1,
                2299.00, SkuCategoryEnum.ELECTRONICS));

            add(new Sku(645321, "纯色衬衫",
                409.00, 3,
                1227.00, SkuCategoryEnum.CLOTHING));

            add(new Sku(654327, "牛仔裤",
                528.00, 1,
                528.00, SkuCategoryEnum.CLOTHING));

            add(new Sku(675489, "跑步机",
                2699.00, 1,
                2699.00, SkuCategoryEnum.SPORTS));

            add(new Sku(644564, "Java编程思想",
                79.80, 1,
                79.80, SkuCategoryEnum.BOOKS));

            add(new Sku(678678, "Java核心技术",
                149.00, 1,
                149.00, SkuCategoryEnum.BOOKS));

            add(new Sku(697894, "算法",
                78.20, 1,
                78.20, SkuCategoryEnum.BOOKS));

            add(new Sku(696968, "TensorFlow进阶指南",
                85.10, 1,
                85.10, SkuCategoryEnum.BOOKS));
        }
    };

    // 获取商品信息列表
    public static List<Sku> getCartSkuList() {
        return cartSkuList;
    }
}

筛选数码产品

接下来女友小丽需要检查其购物车,决定哪些可以买,毕竟经济大权掌握在小丽手中。现在小丽要让小马筛选出所有的数码类商品

首先,要新增数码类产品的筛选方法

/**
  * Version 1.0.0
  * 找出购物车中所有电子产品
  * @param cartSkuList
  * @return
  */
public static List<Sku> filterElectronicsSkus(List<Sku> cartSkuList) {
    List<Sku> result = new ArrayList<Sku>();
    for (Sku sku : cartSkuList) {
        if (SkuCategoryEnum.ELECTRONICS.equals(sku.getSkuCategory())) {
            result.add(sku);
        }
    }
    return result;
}

单元测试调用方法

package lambda.cart;

import com.alibaba.fastjson.JSON;
import org.junit.Test;

import java.util.List;

public class Version1Test {
    @Test
    public void filterElectronicsSkus() {
        List<Sku> cartSkuList = CartService.getCartSkuList();
        // 查找购物车中数码类商品
        List<Sku> result = CartService.filterElectronicsSkus(cartSkuList);
        System.out.println(JSON.toJSONString(result, true));
    }
}

根据类型筛选产品

这种硬编码方式并不好,小丽要换一个类型,小马就要再写一个方法,可以将方法封装为根据类型筛选,让小丽自己传参

/**
  * Version 2.0.0
  * 根据传入商品类型参数,找出购物车中同种商品类型的商品列表
  * @param cartSkuList
  * @param category
  * @return
  */
public static List<Sku> filterSkusByCategory(List<Sku> cartSkuList, SkuCategoryEnum category) {
    List<Sku> result = new ArrayList<Sku>();
    for (Sku sku : cartSkuList) {
        if (category.equals(sku.getSkuCategory())) {
            result.add(sku);
        }
    }
    return result;
}

测试调用

package lambda.cart;

import com.alibaba.fastjson.JSON;
import org.junit.Test;

import java.util.List;

public class Version2Test {
    @Test
    public void filterSkusByCategory() {
        List<Sku> cartSkuList = CartService.getCartSkuList();
        List<Sku> result = CartService.filterSkusByCategory(cartSkuList, SkuCategoryEnum.BOOKS);
        System.out.println(JSON.toJSONString(result, true));
    }
}

添加总价维度

有些商品的价格很贵,小丽想要新增总价维度,比如筛选出价格大于 2000 的商品,小马只好继续写代码...

/**
  * Version 3.0.0
  * 通过商品类型或总价来过滤商品
  * @param cartSkuList
  * @param category
  * @param totalPrice
  * @param categoryOrPrice true:根据商品类型 false: 根据商品总价
  * @return
  */
public static List<Sku> filterSkus(List<Sku> cartSkuList, SkuCategoryEnum category, Double totalPrice, Boolean categoryOrPrice) {
    List<Sku> result = new ArrayList<Sku>();
    for (Sku sku : cartSkuList) {
        if (
            (categoryOrPrice && category.equals(sku.getSkuCategory())) ||  // 根据商品类型判断
            (!categoryOrPrice && sku.getTotalPrice() > totalPrice)  // 根据商品总价判断
        ) {
            result.add(sku);
        }
    }
    return result;
}

测试调用

package lambda.cart;

import com.alibaba.fastjson.JSON;
import org.junit.Test;

import java.util.List;

public class Version3Test {
    @Test
    public void filterSkus() {
        List<Sku> cartSkuList = CartService.getCartSkuList();
        // 根据类型
        List<Sku> result1 = CartService.filterSkus(cartSkuList, SkuCategoryEnum.BOOKS, 0.0, true);
        System.out.println(JSON.toJSONString(result1, true));
        // 根据总价
        List<Sku> result2 = CartService.filterSkus(cartSkuList, null, 2000.0, false);
        System.out.println(JSON.toJSONString(result2, true));
    }
}

将行为参数化

这么折腾下来,也不是事,代码越来越丑,再加多几个条件,将会变得难以维护

能不能将这些判断条件抽象化,服务中只管最终的判断结果,而不管究竟是如何判断的?

/**
  * version 4.0.0
  * 根据不同的 sku 判断标准,对 sku 列表进行过滤
  * @param cartSkuList
  * @param predicate 不同的 sku 判断标准策略
  * @return
  */
public static List<Sku> filterSkus(List<Sku> cartSkuList, SkuPredicate predicate) {
    List<Sku> result = new ArrayList<Sku>();
    for (Sku sku : cartSkuList) {
        // 根据不同的 sku 判断标准策略,对 sku 进行判断
        if (predicate.test(sku)) {
            result.add(sku);
        }
    }
    return result;
}

即,不同的 sku 提供不同的判断标准,服务类只获取结果

sku 的选择谓语是一个抽象的接口

package lambda.cart;

/**
 * sku 选择谓词
 */
public interface SkuPredicate {
    /**
     * 选择判断标准
     * @param sku
     * @return
     */
    boolean test(Sku sku);
}

具体的 sku 去实现此接口

package lambda.cart;

/**
 * 对 sku 的商品类型为图书类的判断标准
 */
public class SkuBooksCategoryPredicate implements SkuPredicate {
    @Override
    public boolean test(Sku sku) {
        return SkuCategoryEnum.BOOKS.equals(sku.getSkuCategory());
    }
}
package lambda.cart;

/**
 * 对 sku 的总价是否超出 2000 作为判断标准
 */
public class SkuTotalPricePredicate implements SkuPredicate {
    @Override
    public boolean test(Sku sku) {
        return sku.getTotalPrice() > 2000;
    }
}

现在要调用大于 2000 的商品,代码如下:

package lambda.cart;

import com.alibaba.fastjson.JSON;
import org.junit.Test;

import java.util.List;

public class Version4Test {
    @Test
    public void filterSkus() {
        List<Sku> cartSkuList = CartService.getCartSkuList();
        // 过滤总价大于 2000 的商品
        List<Sku> result2 = CartService.filterSkus(cartSkuList, new SkuTotalPricePredicate());
        System.out.println(JSON.toJSONString(result2, true));
    }
}

经此拆分,CartService 的代码就很简洁。将要判断的具体逻辑作为参数传递,即:行为参数化

匿名类

以上的行为参数化过于复杂,要实例化很多类,但思想是很优秀的,我们可以传递匿名类,就不用去写那么多的实现类

package lambda.cart;

import com.alibaba.fastjson.JSON;
import org.junit.Test;

import java.util.List;

public class Version5Test {
    @Test
    public void filterSkus() {
        List<Sku> cartSkuList = CartService.getCartSkuList();
        // 用匿名类传递参数
        // 不用对接口进行实现,不需要写一堆多余的类
        List<Sku> result2 = CartService.filterSkus(cartSkuList, new SkuPredicate() {
            @Override
            public boolean test(Sku sku) {
                return sku.getSkuPrice() > 3000;
            }
        });
        System.out.println(JSON.toJSONString(result2, true));
    }
}

lambda 表达式进行优化

还可以再用 lambda 表达式进行优化

package lambda.cart;

import com.alibaba.fastjson.JSON;
import org.junit.Test;

import java.util.List;

public class Version6Test {
    @Test
    public void filterSkus() {
        List<Sku> cartSkuList = CartService.getCartSkuList();
        List<Sku> result = CartService.filterSkus(cartSkuList, (Sku sku) -> sku.getSkuPrice() > 1000);
        System.out.println(JSON.toJSONString(result, true));
    }
}

至此,CartService 不需要再去定义复杂的判断条件,调用方直接用 lambda 表达式进行传参即可

内置的函数式接口

jdk 已经为我们定义了现成的函数式接口

在 java.util.function 包下有大量的内置函数式接口,如:

  • XxxFunction:这类接口中通常包含一个apply()抽象方法,该方法对参数进行处理、转换(apply()方法的处理逻辑由Lambda表达式来实现),然后返回一个新的值。该函数式接口通常用于对指定数据进行转换处理

  • XxxConsumer:这类接口中通常包含一个accept()抽象方法,该方法与XxxFunction接口中的apply()方法基本相似,也负责对参数进行处理,只是该方法不会返回处理结果

  • XxxxPredicate:这类接口中通常包含一个test()抽象方法,该方法通常用来对参数进行某种判断(test()方法的判断逻辑由Lambda表达式来实现),然后返回一个boolean值。该接口通常用于判断参数是否满足特定条件,经常用于进行筛滤数据

  • XxxSupplier:这类接口中通常包含一个getAsXxx()抽象方法,该方法不需要输入参数,该方法会按某种逻辑算法(getAsXxx ()方法的逻辑算法由Lambda表达式来实现)返回一个数据

自定义函数式接口

此外,我们也可以自定义函数式接口

package lambda.cart.file;

@FunctionalInterface
public interface FileConsumer {
    /**
     * 函数式接口抽象方法
     * @param fileContent
     */
    void fileHandler(String fileContent);
}

文件服务类

package lambda.cart.file;

import org.apache.commons.lang3.text.StrBuilder;

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;

/**
 * 文件服务类
 */
public class FileService {
    /**
     * 通过 url 获取本地文件内容
     * @param url
     * @param fileConsumer
     */
    public void fileHandle(String url, FileConsumer fileConsumer) throws IOException {
        // 创建文件读取流
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(new FileInputStream(url)));

        // 定义行变量和内容
        String line;
        StringBuilder stringBuilder = new StringBuilder();

        // 循环读取文件内容
        while ((line = bufferedReader.readLine()) != null) {
            stringBuilder.append(line + "\n");
        }

        // 调用函数式接口方法,将文件内容传递给 lambda 表达式,实现业务逻辑
        fileConsumer.fileHandler(stringBuilder.toString());
    }
}

测试调用

package lambda.file;

import lambda.cart.file.FileService;
import org.junit.Test;

import java.io.IOException;

public class FileServiceTest {
    @Test
    public void fileHandle() throws IOException {
        FileService fileService = new FileService();
        // 通过 lambda 表达式,打印文件内容
        fileService.fileHandle("/Users/senlongzhong/code/demo/javaDemo/src/test/java/lambda/file/FileServiceTest.java", fileContent -> System.out.println(fileContent));
    }
}
关于我
一个文科出身的程序员,追求做个有趣的人,传播有价值的知识,微信公众号主要分享读书思考心得,不会有代码类文章,非程序员的同学请放心订阅
转载须注明出处:https://www.itshutong.com/articles/1022