多筛选条件搜索场景下的设计模式选型及搜索函数重载优化问询
多筛选条件搜索场景下的设计模式选型及搜索函数重载优化问询
嘿,这个问题问得特别戳痛点——我之前做健身类垂直项目的时候,刚好碰到过几乎一模一样的场景,当时也纠结过要不要硬写一堆重载函数,后来用了两个设计模式的组合完美解决了,分享给你:
先说说为啥别写一堆重载函数
- 首先,函数爆炸问题:如果有n个筛选条件,组合数是2ⁿ-1,比如你说的4个条件(距离、邮编、名称、特色),就有15种组合,这还没算后续加新条件的情况(比如要不要24小时营业、有没有儿童区),到时候类里的函数会多到你自己都找不到,维护起来纯纯的噩梦。
- 其次,违反开闭原则:每次加新的筛选条件,你都得回去改原有的类,加新的重载函数,很容易不小心改坏旧的逻辑。
推荐的两种解决方案
1. Builder模式(适合简单的多可选条件)
核心思路是把所有筛选条件打包成一个“查询参数对象”,用Builder来构建这个对象,搜索函数只需要接受这个对象作为参数。
举个Java风格的示例代码(其他语言比如Python、JS同理,只是语法不同):
// 定义查询条件的Builder类 public class GymSearchCriteria { private Integer maxDistance; // 最大距离 private String pincode; private String nameKeyword; private Set<String> requiredFeatures; // 私有构造,只能通过Builder创建 private GymSearchCriteria(Builder builder) { this.maxDistance = builder.maxDistance; this.pincode = builder.pincode; this.nameKeyword = builder.nameKeyword; this.requiredFeatures = builder.requiredFeatures; } // 对外暴露的Builder public static class Builder { private Integer maxDistance; private String pincode; private String nameKeyword; private Set<String> requiredFeatures; // 每个条件的设置方法,返回this支持链式调用 public Builder maxDistance(Integer distance) { this.maxDistance = distance; return this; } public Builder pincode(String pincode) { this.pincode = pincode; return this; } public Builder nameKeyword(String keyword) { this.nameKeyword = keyword; return this; } public Builder requiredFeatures(Set<String> features) { this.requiredFeatures = features; return this; } // 构建最终的查询条件对象 public GymSearchCriteria build() { return new GymSearchCriteria(this); } } }
然后你的搜索函数只需要一个:
public List<Gym> searchGyms(GymSearchCriteria criteria) { // 内部逻辑:判断哪些条件不为空,动态拼接查询(比如数据库的WHERE子句,或者内存过滤) // 比如用MyBatis的话,就用<if test="maxDistance != null">来拼接条件 // 内存过滤的话,就逐个判断criteria的属性 }
调用的时候特别优雅,想加什么条件就链什么:
// 只按距离搜 searchGyms(new GymSearchCriteria.Builder().maxDistance(5).build()); // 按距离+名称搜 searchGyms(new GymSearchCriteria.Builder().maxDistance(5).nameKeyword("阳光").build()); // 按邮编+特色搜 searchGyms(new GymSearchCriteria.Builder().pincode("100001").requiredFeatures(Set.of("泳池", "瑜伽室")).build());
2. Specification模式(适合复杂的组合筛选)
如果你的筛选逻辑不止是“同时满足多个条件”,还需要“满足A或者满足B”“不满足C”这种复杂逻辑,那Specification模式就太合适了。
核心是把每个筛选条件封装成一个独立的Specification类,然后可以自由组合这些条件(与、或、非)。
先定义一个Specification接口:
public interface GymSpecification { // 判断某个健身房是否符合该条件 boolean isSatisfiedBy(Gym gym); // 组合方法:与 default GymSpecification and(GymSpecification other) { return gym -> this.isSatisfiedBy(gym) && other.isSatisfiedBy(gym); } // 组合方法:或 default GymSpecification or(GymSpecification other) { return gym -> this.isSatisfiedBy(gym) || other.isSatisfiedBy(gym); } // 组合方法:非 default GymSpecification not() { return gym -> !this.isSatisfiedBy(gym); } }
然后实现每个具体的条件:
// 距离条件 public class MaxDistanceSpecification implements GymSpecification { private Integer maxDistance; public MaxDistanceSpecification(Integer maxDistance) { this.maxDistance = maxDistance; } @Override public boolean isSatisfiedBy(Gym gym) { return gym.getDistanceFromUser() <= maxDistance; } } // 名称条件 public class NameContainsSpecification implements GymSpecification { private String keyword; public NameContainsSpecification(String keyword) { this.keyword = keyword.toLowerCase(); } @Override public boolean isSatisfiedBy(Gym gym) { return gym.getName().toLowerCase().contains(keyword); } }
搜索函数只需要接受一个Specification:
public List<Gym> searchGyms(GymSpecification spec) { List<Gym> allGyms = getAllGymsFromDB(); // 从数据库拿所有健身房 return allGyms.stream() .filter(spec::isSatisfiedBy) .collect(Collectors.toList()); }
调用的时候可以自由组合复杂逻辑:
// 距离不超过5公里,或者名称包含“阳光”,并且不在邮编100002的区域 GymSpecification spec = new MaxDistanceSpecification(5) .or(new NameContainsSpecification("阳光")) .and(new GymSpecification() { @Override public boolean isSatisfiedBy(Gym gym) { return !gym.getPincode().equals("100002"); } }); searchGyms(spec);
最后给你个小总结
- 如果只是简单的“可选条件组合”,Builder模式足够用,代码简洁易上手,维护成本低;
- 如果需要复杂的逻辑组合(与、或、非),Specification模式是最优解,完全解耦筛选逻辑,扩展性拉满;
- 绝对不要写一堆重载函数,除非你能保证筛选条件永远不会变——但现实中几乎不可能。
内容来源于stack exchange
相关产品推荐
相关产品推荐

