zoukankan      html  css  js  c++  java
  • 字符串解析与集合过滤的抽象

    问题如下

    假设我们有一个类HotelCenter,它有方法List<Hotel> getHotelList(String city)可以获得一个城市下所有酒店列表
    Hotel类有如下属性String name, String address, int price, Map<String, String> properties
    酒店的非name和address的属性都可以通过properties.get(key)得到
    我们需要实现以下功能
    根据页面传入的参数返回对应的酒店列表,参数键值对会以&分割,参数的值如果有多个,会有逗号分隔
    下述任何参数都可能缺失,对应的值如果为空串,也当做该参数不存在处理
    参数会分三部分组成,过滤参数、排序参数和翻页参数
    过滤参数包括
    city(酒店所在城市)
    name(酒店名包括name的值)
    address(酒店地址包括address的值)
    brand(酒店品牌属于brand的值的一个)
    star(酒店星级属于star的值中的一个)
    price(酒店价格在price值区间范围内)
    area(酒店所属区域等于area值中的一个)
    排序参数包括
    sort(按照sort的值进行排序,如果值是price,就按照价格进行排序,如果值是star,则按照星级进行排序)
    order(值如果是asc就是升序,是desc就是降序)
    排序参数缺失时,默认按照sort=price&order=asc处理
    翻页参数包括
    page(page的值是需要看的酒店其实索引值和终止索引值,是左闭右开,如果选择的索引没有数据,则不处理,比如一共有30个酒店,page=20-40,需要返回后10个酒店)
    翻页参数缺失时,默认按照0-20处理
    以下是一个请求参数的例子
    city=北京&name=酒店&address=海淀黄庄&brand=7天,如家&star=3,4&price=100-200,300-500&area=中关村&sort=price&order=desc&page=100-120

    问题分析

    程序行为

    本程序需要实现的功能有3点:

    1. 过滤集合元素

    2. 排序集合元素

    3. 截取集合子集

    为了实现上述三个三个功能, 需要先实现:

    1. 读取酒店原始数据

    2. 解析URL, 构造查询条件

    抽象分析

    实际上过滤, 排序, 获取子集三个功能的条件都来自于URL, 而不难看出URL中的条件具有一定的共通性:

    1. "&" 表示"与"关系条件

    2. "," 表示"或"关系条件

    3. "-" 表示范围条件

    按照这个约定对URL进行解析,上述URL

    city=北京&name=酒店&address=海淀黄庄&brand=7天,如家&star=3,4&price=100-200,300-500&area=中关村&sort=price&order=desc&page=100-120

    可转化为

    (city = 北京) && (name = 酒店) && (brand = 7天 || brand = 如家) ... 

    我们可以将每项条件抽象为一个对象, 并对这些对象加以组合(以Or的方式, 和And的方式)

    另外,当将条件抽象为对象以后,我们需要知道这些对象需要过滤的字段对应到Hotel类中的成员变量是哪个字段, 这一点可以通过反射实现

    代码实现

    Hotel实体

    /*
     * Copyright (c) 2013 Qunar.com. All Rights Reserved.
     */
    
    package com.qunar.task3.bean;
    
    import java.util.HashMap;
    import java.util.Locale;
    import java.util.Map;
    
    /**
     * @author zhenwei.liu created on 2013 13-10-15 下午8:52
     * @version 1.0.0
     */
    public class Hotel {
        private static final String CITY_KEY = "city";
        private static final String BRAND_KEY = "brand";
        private static final String STAR_KEY = "star";
        private static final String AREA_KEY = "area";
    
        private String name;
        private String address;
        private Integer price;
        private Map<String, String> properties = new HashMap<String, String>();
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public String getAddress() {
            return address;
        }
    
        public void setAddress(String address) {
            this.address = address;
        }
    
        public Integer getPrice() {
            return price;
        }
    
        public void setPrice(int price) {
            this.price = price;
        }
    
        public String getCity() {
            return properties.get(CITY_KEY);
        }
    
        public void setCity(String city) {
            properties.put(CITY_KEY, city);
        }
    
        public String getBrand() {
            return properties.get(BRAND_KEY);
        }
    
        public void setBrand(String brand) {
            properties.put(BRAND_KEY, brand);
        }
    
        public String getStar() {
            return properties.get(STAR_KEY);
        }
    
        public void setStar(String star) {
            properties.put(STAR_KEY, star);
        }
    
        public String getArea() {
            return properties.get(AREA_KEY);
        }
    
        public void setArea(String area) {
            properties.put(AREA_KEY, area);
        }
    
        public String getProperty(String key) {
            return properties.get(key);
        }
    
        @Override
        public String toString() {
            return String.format(Locale.SIMPLIFIED_CHINESE, "%s		%s		%s		%s		%s		%s		%s		", name, address, price, getCity(), getArea(),
                    getBrand(), getStar());
        }
    }

    Range范围类

    package com.qunar.task3.util;
    
    /**
     * 表示一个数值范围的类
     * 
     * @author zhenwei.liu created on 2013 13-10-16 上午12:15
     * @version 1.0.0
     */
    public class Range {
        private int floor;
        private int ceiling;
    
        public Range(int floor, int ceiling) {
            if (floor > ceiling)
                throw new IllegalArgumentException("floor must be less than or equals to ceiling");
            this.floor = floor;
            this.ceiling = ceiling;
        }
    
        /**
         * 范围判断,左闭右开
         * 
         * @param i
         * @return
         */
        public boolean inRange(int i) {
            return i < ceiling && i > floor;
        }
    
        public int getFloor() {
            return floor;
        }
    
        public void setFloor(int floor) {
            this.floor = floor;
        }
    
        public int getCeiling() {
            return ceiling;
        }
    
        public void setCeiling(int ceiling) {
            this.ceiling = ceiling;
        }
    }

    Hotel工具类

    /*
     * Copyright (c) 2013 Qunar.com. All Rights Reserved.
     */
    
    package com.qunar.task3.util;
    
    import java.lang.reflect.Field;
    import java.util.Collection;
    import java.util.Comparator;
    import java.util.HashMap;
    import java.util.Map;
    
    import com.qunar.task3.bean.Hotel;
    
    /**
     * Hotel工具类
     * 
     * @author zhenwei.liu created on 2013 13-10-15 下午9:16
     * @version 1.0.0
     */
    public class HotelUtils {
        private static final String ORDER_DESC = "desc";
        private static final Map<String, Field> fieldMap = new HashMap<String, Field>();
    
        static {
            Field[] fields = Hotel.class.getDeclaredFields();
            for (int i = 0; i < fields.length; i++) {
                Field field = fields[i];
                field.setAccessible(true);
                fieldMap.put(field.getName(), field);
            }
        }
    
        /**
         * 通过成员变量名获取变量值
         * 
         * @param filed 待获取的变量值的变量名
         * @param hotel 拥有field的hotel变量
         * @return 由field指定的hotel成员变量值
         * @throws IllegalAccessException
         */
        public static Object getFieldVal(String filed, Hotel hotel) throws IllegalAccessException {
            return hotel.getProperty(filed) == null ? fieldMap.get(filed).get(hotel) : hotel.getProperty(filed);
        }
    
        /**
         * 通过成员变量名获取比较器
         * 
         * @param filed 需要比较Hotel属性
         * @return 比较器
         */
        public static Comparator<Hotel> getComparator(final String filed, final String order) throws IllegalAccessException {
    
            if (fieldMap.get(filed) == null)
                throw new IllegalAccessException();
    
            return new Comparator<Hotel>() {
    
                @Override
                public int compare(Hotel o1, Hotel o2) {
                    int result = 0;
    
                    if (o1.getProperty(filed) != null) { // 对位于Map内的参数排序
                        result = o1.getProperty(filed).compareTo(o2.getProperty(filed));
                    } else {
                        // 对其余属性排序
                        try {
                            result = getFieldVal(filed, o1).toString().compareTo(getFieldVal(filed, o2).toString());
                        } catch (IllegalAccessException e) { // 永远不会抛出此异常
                            e.printStackTrace();
                            // ignored
                        }
                    }
    
                    return order.equals(ORDER_DESC) ? -result : result;
                }
            };
        }
    
        /**
         * 根据list校正range范围
         * 
         * @param collection 用于校验的集合
         * @param range 等待校验的range
         * @return 校验完成的range
         */
        public static Range fixRange(Collection collection, Range range) {
            if (range.getFloor() < 0)
                range.setFloor(0);
            if (range.getCeiling() > collection.size())
                range.setCeiling(collection.size());
            return range;
        }
    
    }

    表示过滤条件的Filter接口

    package com.qunar.task3.filter;
    
    /**
     * 过滤器接口
     * 
     * @author zhenwei.liu created on 2013 13-10-15 下午11:31
     * @version 1.0.0
     */
    public interface Filter<T> {
    
        /**
         * 过滤方法,用于表示待过滤元素是否符合条件
         * 
         * @param t 待过滤元素
         * @return
         */
        boolean apply(T t);
    }

    集合过滤操作以及组合Filter的实现, Filters.java

    package com.qunar.task3.filter;
    
    import java.util.Arrays;
    import java.util.Collection;
    import java.util.LinkedList;
    import java.util.List;
    
    /**
     * 过滤器工具类
     * 
     * @author zhenwei.liu created on 2013 13-10-15 下午11:32
     * @version 1.0.0
     */
    public class Filters {
    
        /**
         * 过滤方法, 使用指定过滤器过滤集合元素
         * 
         * @param iterable 带过滤集合
         * @param filter 过滤器
         * @param <T> 待过滤元素类型
         * @return 过滤完成后的元素集合
         */
        public static <T> Iterable<T> filter(Iterable<T> iterable, Filter<T> filter) {
            List<T> list = new LinkedList<T>((Collection<T>) iterable);
    
            for (T t : iterable) {
                if (!filter.apply(t))
                    list.remove(t);
            }
    
            return list;
        }
    
        /**
         * 返回一个判断永远为true的过滤器
         * 
         * @param <T> 待过滤元素类型
         * @return 过滤器
         */
        public static <T> Filter<T> alwaysTrue() {
            return new Filter<T>() {
    
                @Override
                public boolean apply(T t) {
                    return true;
                }
            };
        }
    
        /**
         * 返回一个判断永远为false的过滤器
         * 
         * @param <T> 待过滤元素类型
         * @return 过滤器
         */
        public static <T> Filter<T> alwaysFalse() {
            return new Filter<T>() {
    
                @Override
                public boolean apply(T t) {
                    return false;
                }
            };
        }
    
        /**
         * 将多个过滤器组合为OrFilter
         * 
         * @param filters 待组合过滤器数组
         * @param <T> 待过滤元素类型
         * @return OrFilter过滤器
         */
        public static <T> Filter<T> or(Filter<T>... filters) {
            return new OrFilter<T>(Arrays.asList(filters));
        }
    
        /**
         * 将多个过滤器组合为AndFilter
         * 
         * @param filters 待组合过滤器数组
         * @param <T> 待过滤元素类型
         * @return AndFilter过滤器
         */
        public static <T> Filter<T> and(Filter<T>... filters) {
            return new AndFilter<T>(Arrays.asList(filters));
        }
    
        /**
         * "或"组合过滤器
         * 
         * @param <E>
         */
        private static class OrFilter<E> implements Filter<E> {
    
            private final List<? extends Filter<? super E>> components;
    
            /**
             * 组合多个过滤器
             * 
             * @param components 待组合过滤器集合
             */
            private OrFilter(List<? extends Filter<? super E>> components) {
                this.components = components;
            }
    
            /**
             * 过滤方法,依次调用过滤器集合每个过滤器的过滤方法,以"或"的方式组合
             * 
             * @param e 待过滤元素
             * @return 过滤结果
             */
            @Override
            public boolean apply(E e) {
                for (int i = 0; i < components.size(); i++) {
                    if (components.get(i).apply(e))
                        return true;
                }
                return false;
            }
        }
    
        /**
         * "与"组合过滤器
         * 
         * @param <E>
         */
        private static class AndFilter<E> implements Filter<E> {
    
            private final List<? extends Filter<? super E>> components;
    
            /**
             * 组合多个过滤器
             * 
             * @param components 待组合过滤器集合
             */
            private AndFilter(List<? extends Filter<? super E>> components) {
                this.components = components;
            }
    
            /**
             * 过滤方法,依次调用过滤器集合每个过滤器的过滤方法,以"与"的方式组合
             * 
             * @param e 待过滤元素
             * @return 过滤结果
             */
            @Override
            public boolean apply(E e) {
                for (int i = 0; i < components.size(); i++) {
                    if (!components.get(i).apply(e))
                        return false;
                }
                return true;
            }
        }
    }

     测试类

    package com.qunar.task3;
    
    import java.io.BufferedReader;
    import java.io.FileNotFoundException;
    import java.io.FileReader;
    import java.io.IOException;
    import java.util.*;
    
    import com.qunar.task3.bean.Hotel;
    import com.qunar.task3.filter.Filter;
    import com.qunar.task3.filter.Filters;
    import com.qunar.task3.util.HotelUtils;
    import com.qunar.task3.util.Range;
    
    /**
     * 酒店中心,用于处理查询参数,筛选符合条件的酒店
     * 
     * @author zhenwei.liu created on 2013 13-10-15 下午8:53
     * @version 1.0.0
     */
    public class HotelCenter {
        private static final Set<String> FILTER_PARAMS = new HashSet<String>();
        private static final List<Hotel> HOTEL_LIST = new ArrayList<Hotel>();
        private static final String SORT_KEY = "sort";
        private static final String ORDER_KEY = "order";
        private static final String PAGE_KEY = "page";
    
        // 准备酒店数据
        static {
            FILTER_PARAMS.add("city");
            FILTER_PARAMS.add("name");
            FILTER_PARAMS.add("address");
            FILTER_PARAMS.add("brand");
            FILTER_PARAMS.add("star");
            FILTER_PARAMS.add("price");
            FILTER_PARAMS.add("area");
    
            BufferedReader br = null;
            try {
                br = new BufferedReader(new FileReader(System.getProperty("user.dir")
                        + "\src\main\java\com\qunar\task3\hotels.txt"));
                String s;
                while ((s = br.readLine()) != null) {
                    Hotel hotel = new Hotel();
                    String[] ss = s.split(";");
                    hotel.setName(ss[0]);
                    hotel.setAddress(ss[1]);
                    hotel.setCity(ss[2]);
                    hotel.setPrice(Integer.parseInt(ss[3]));
                    hotel.setBrand(ss[4]);
                    hotel.setStar(ss[5]);
                    hotel.setArea(ss[6]);
                    HOTEL_LIST.add(hotel);
                }
            } catch (FileNotFoundException e) {
                e.printStackTrace();
                System.out.println("Hotel info file not found");
                System.exit(-1);
            } catch (IOException e) {
                e.printStackTrace();
                System.out.println("Error occur while reading hotel info ");
                System.exit(-1);
            } finally {
                if (br != null)
                    try {
                        br.close();
                    } catch (IOException e) {
                        // ignored
                    }
            }
        }
    
        private List<Hotel> result;
        private Range pagination;
        private Comparator<Hotel> comparator;
        private Filter<Hotel> filter = Filters.alwaysTrue();
        private Map<String, String> paramsMap = new HashMap<String, String>();
    
        public HotelCenter(String paramStr) {
            // 参数解析,不考虑参数非法情况
            String[] ss = paramStr.split("&");
            for (String s : ss) {
                String[] ss2 = s.split("=");
                if (ss2.length == 2)
                    paramsMap.put(ss2[0], ss2[1]);
            }
    
            initFilter();
            initComparator();
            initPagination();
        }
    
        /**
         * 获取包含所有过滤条件的过滤器 解析一次,无限使用
         * 
         * @return
         */
        private void initFilter() {
            for (final String s : paramsMap.keySet()) {
                if (FILTER_PARAMS.contains(s)) { // 仅处理过滤参数
                    // ","分隔的参数使用OrFilter连接
                    String[] ss = paramsMap.get(s).split(",");
                    Filter<Hotel> tmpFilter = Filters.alwaysFalse();
                    for (int i = 0; i < ss.length; i++) {
                        String s1 = ss[i];
                        final String[] ss1 = s1.split("-");
                        if (ss1.length > 1) { // 处理范围参数,使用range包装
                            tmpFilter = Filters.or(tmpFilter, new Filter<Hotel>() {
    
                                @Override
                                public boolean apply(Hotel hotel) {
                                    try {
                                        return new Range(Integer.valueOf(ss1[0]), Integer.valueOf(ss1[1])).inRange(Integer
                                                .valueOf(HotelUtils.getFieldVal(s, hotel).toString()));
                                    } catch (IllegalAccessException e) {
                                        return true;
                                    }
                                }
                            });
                        } else { // 处理单个参数
                            tmpFilter = Filters.or(tmpFilter, new Filter<Hotel>() {
    
                                @Override
                                public boolean apply(Hotel hotel) {
                                    try {
                                        return HotelUtils.getFieldVal(s, hotel).toString().contains(ss1[0]);
                                    } catch (IllegalAccessException e) {
                                        return true;
                                    }
                                }
                            });
                        }
                    }
    
                    // "&"分隔的参数使用AndFilter连接
                    filter = Filters.and(filter, tmpFilter);
                }
    
            }
        }
    
        public void initComparator() {
            try {
                comparator = HotelUtils.getComparator(paramsMap.get(SORT_KEY), paramsMap.get(ORDER_KEY));
            } catch (IllegalAccessException e) {
                e.printStackTrace();
                System.out.println("Illegal access field: " + paramsMap.get(SORT_KEY));
            }
        }
    
        public void initPagination() {
            String[] ss = paramsMap.get(PAGE_KEY).split("-");
            pagination = new Range(Integer.valueOf(ss[0]), Integer.valueOf(ss[1]));
        }
    
        public Iterable<Hotel> getHotelResult() {
            if (result != null)
                return result;
            result = new ArrayList<Hotel>((Collection<Hotel>) Filters.filter(HOTEL_LIST, filter));
            Collections.sort(result, comparator);
            pagination = HotelUtils.fixRange(result, pagination);
            result = result.subList(pagination.getFloor(), pagination.getCeiling());
            return result;
        }
    
        public static void main(String[] args) {
            String params = "city=北京&sort=price&price=100-200,300-400&order=desc&page=2-8";
            HotelCenter hc = new HotelCenter(params);
            for (Hotel hotel : hc.getHotelResult()) {
                System.out.println(hotel);
            }
        }
    }

    总结

    这样抽象的好处是以后可扩展性良好, 加入了新的过滤字段条件后, 只需要修改Hotel的bean字段, 并将该字段添加到FILTER_PARAMS中即可

    当然也可以使用简单暴力的多重if来过滤这些字段 :D

  • 相关阅读:
    ul内的li横向排列左右滑动
    git使用中常见报错解决方法
    vue 中子组件用watch方法监听父组件传来的参数,用handler方法绑定,当父组件参数不改变时,handler方法不执行
    [Vue warn]: Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders.
    vue 动态更改classname
    react怎么添加运用自定义组件
    app项目中遇到TCP分包,H5端对分包进行拼包
    Error: Invalid CSS after "xxx": expected 1 selector or at-rule, was "{}"
    'touch' 不是内部或外部命令,也不是可运行的程序或批处理文件。
    微信小程序调用快递物流查询API的实现方法
  • 原文地址:https://www.cnblogs.com/zemliu/p/3372657.html
Copyright © 2011-2022 走看看