泛型、回调思想的应用

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/xsp_happyboy/article/details/81743503

1 场景

相信很多做电商的小伙伴们都会遇到这样一个需求:查出所有的手机集合,然后按照手机的批次字段,对这个集合进行分组。

2 一般的实现方法

手机类:

public class MobilePhone {

    /**
     * 主键
     */
    private String id;

    /**
     * 手机名称
     */
    private String phoneName;

    /**
     * 手机生产代号
     */
    private String phoneCode;

    /**
     * 品牌
     */
    private String brand;

    /**
     * 类别
     */
    private String category;

    /**
     * 批号
     */
    private String batchNo;

    /**
     * 此处省略get、set方法
     */
}

分组方法:

/**
 * 商品list,按照批号分组
 * @param mobilePhones 商品List
 * @return 分组后的商品
 */
public Map<String,List<MobilePhone>> groupBy(List<MobilePhone> mobilePhones){
    Map<String,List<MobilePhone>> phoneMap = new HashMap<>();
    for (MobilePhone mobilePhone : mobilePhones) {
        List<MobilePhone> phoneList = phoneMap.get(mobilePhone.getBatchNo());
        if(phoneList == null){
            phoneList = new ArrayList<>();
            phoneMap.put(mobilePhone.getBatchNo(),phoneList);
        }
        phoneList.add(mobilePhone);
    }
    return phoneMap;
}

顺便说一下,Java8以后,Map 有一个computeIfAbsent方法,可以对以上方法进行简化:

public Map<String,List<MobilePhone>> groupBy2(List<MobilePhone> mobilePhones){
        Map<String,List<MobilePhone>> phoneMap = new HashMap<>();
        for (MobilePhone mobilePhone : mobilePhones) {
            List<MobilePhone> phoneList = 
            phoneMap.computeIfAbsent(mobilePhone.getBatchNo(), k -> new ArrayList<>());
            phoneList.add(mobilePhone);
        }
        return phoneMap;
    }

3 对方法进行抽象

由于产品的需求会经常改变,譬如说,现在又要求所有的手机按照品牌进行分组?又譬如说,要求手机按照类别进行分组?再者,又要求给电脑进行分组?像上面那样去实现,岂不是要写N个实现方法呢?

下面咱们聊聊,怎么对方法进行抽象。

抽象分析
图中的标号1、2、3处都可以用范型直接进行抽象,问题是标号4处的方法名会经常变化,难以抽象。

猜想:可否在经常变化的方法名外面封装一层固定不变的方法呢?标号4处可以用固定的方法名代替,但是不同的对象仍需要调用特定的方法名去获得属性值,因而可以采用回调函数的思想。

3.1 回调函数

简单的聊一聊回调函数

回调的过程一般由三个角色参与:初始函数(一般称之为主函数)、中间函数、回调函数。

举个生活中的例子进行理解:旅客去酒店住宿,怕早上睡过头,需要酒店老板提供叫醒服务(叫醒服务就是中间函数),但叫醒的方式(叫醒的方式就是回调函数)需由旅客指定,比如打电话叫醒,敲门叫醒,或者怕睡得太死泼冷水叫醒。

伪代码示例:

//回调函数一,打电话叫醒
void phoneWake(Guest g);

//回调函数二,敲门叫醒
void knockWake(Guest g);

//回调函数三,泼冷水叫醒
void waterWake(Guest g);
//中间函数
void wakeService(Guest g,wakeMethod wake){
    //中间函数调用回调函数
    wake(g);
}
//初始函数(主函数)
main(){
    //初始函数中调用中间函数,采用泼冷水的方式叫醒
    wakeService(g,waterWake);
}

3.2 具体实现

回到之前的问题,标号4处代码:mobilePhone.getBatchNo(),由于属性名不同getBatchNo会经常变化,需要在方法的外面再封装一层名称固定不变的方法。

Java8中有已经定义好的函数接口咱们可以直接拿来用!

@FunctionalInterface
public interface Function<T, R> {
    R apply(T t);
}

Function中的apply方法就是咱们需要的固定不变的方法名,T就是实例中的MobilePhoneR就是getBatch()返回的String类型。

在调用groupBy 通用方法的时候,根据场景的不同,去实现Function 函数接口的R apply(T t) 方法,并把它作为参数传进来。例如:

groupBy(phones, new Function<MobilePhone, String>() {
            public String apply(MobilePhone mobilePhone){
                return mobilePhone.getBatchNo();
            }
        });

在Java8以后,推荐使用Lambda表达式代替匿名内部类

Talk is cheap, show me the code!!!

/**
 * 数据分组的通用方法
 * @param list list数据集合
 * @param action 函数接口
 * @param <T> 分组属性值
 * @param <K> 分组数据
 * @return 分组结果
 */
public static <T,K> Map<T,List<K>> groupBy(List<K> list,Function<K,T> action){
    Map<T,List<K>> map = new HashMap<>();
    for (K k : list) {
        List<K> groupList = map.get(action.apply(k));
        if(groupList == null){
            groupList = new ArrayList<>();
            map.put(action.apply(k),groupList);
        }
        groupList.add(k);
    }
    return map;
}

测试用例:

public static void main(String args[]){

    // 这里就假装从数据库中查出了数据哇
    List<MobilePhone> phones = new ArrayList<>();
    // 利用lambda表达式去实现Function函数接口
    Map<String,List<MobilePhone>> groupPhone =
            ListUtil.groupBy(phones, e -> e.getBatchNo());

    // 下面是lambda表达式的另一种写法
    // 编译器会把getBatchNo方法的调用者当作参数传给apply方法,然后得到其方法的返回值
    // Map<String,List<MobilePhone>> groupPhone =
    // ListUtil.groupBy(phones, MobilePhone::getBatchNo);

}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章