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