问题如下
假设我们有一个类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