字符串解析与集合过滤的抽象

问题如下

假设我们有一个类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\t\t%s\t\t%s\t\t%s\t\t%s\t\t%s\t\t%s\t\t", 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

你可能感兴趣的:(字符串)