Объект Criterion можно заменить на Спецификация .
Спецификация - это шаблон проектирования, который позволяет объединять критерии с использованием логических операций в более сложные логические композиции.
Скажем, мы хотим отфильтровать все города, включая только те, которые имеют:
- Население выше X;
- но ниже Y;
- Независимо от
население, мы хотим включить все города, где больше женщин
затем мужчины.
Нам нужно разместить где-нибудь логику вроде:
population > x && population < y || men < women
Спецификация - это просто интерфейс с одним методом isSatisfiedBy(city)
, который определяет, соответствует ли объект всем критериям и должен ли он быть включен в список результатов.
interface Specification<T> {
public boolean isSatisfiedBy(T t);
}
Затем нам нужно иметь несколько объектов логической композиции, каждый из которых реализует интерфейс Specification
:
class Or<T> implements Specification<T> {
Specification<T> left;
Specification<T> right;
Or(Specification<T> left, Specification<T> right) {
this.left = left;
this.right = right;
}
@Override
public boolean isSatisfiedBy(T t) {
return left.isSatisfiedBy(t) || right.isSatisfiedBy(t);
}
}
class And<T> implements Specification<T> {
Specification<T> left;
Specification<T> right;
And(Specification<T> left, Specification<T> right) {
this.left = left;
this.right = right;
}
@Override
public boolean isSatisfiedBy(T t) {
return left.isSatisfiedBy(t) && right.isSatisfiedBy(t);
}
}
class Not<T> implements Specification<T> {
Specification<T> specification;
Not(Specification<T> specification) {
this.specification = specification;
}
@Override
public boolean isSatisfiedBy(T t) {
return !this.specification.isSatisfiedBy(t);
}
}
Тогда нам понадобятся базовые спецификации, в которых можно указать необходимые свойства города.
class MaxPopulation implements Specification<City> {
private final int max;
MaxPopulation(int max) {
this.max = max;
}
@Override
public boolean isSatisfiedBy(City city) {
return city.getTotal() <= this.max;
}
}
class MinPopulation implements Specification<City> {
private final int min;
MinPopulation(int min) {
this.min = min;
}
@Override
public boolean isSatisfiedBy(City city) {
return city.getTotal() >= this.min;
}
}
class HasMoreMen implements Specification<City> {
@Override
public boolean isSatisfiedBy(City city) {
return city.getMen() >= city.getWomen();
}
}
И эти городские спецификации объединяются вместе, используя ранее определенные логические спецификации, поэтому наша объединенная спецификация будет выглядеть так:
Specification<City> spec = new Or<City>(
new And<City> (
new MinPopulation(60000),
new MaxPopulation(100000)
),
new Not<City>(new HasMoreMen())
);
После того, как фильтрация будет легкой, нам нужно передать спецификации параметр:
List<City> filtered = cities.stream().filter(city -> spec.isSatisfiedBy(city))
.collect(Collectors.toList());
for (City eachCity : filtered) {
System.out.println(eachCity.getName());
}
Критерий объекта из исходного вопроса может быть выражен в виде Спецификации, но мы можем гибко настраивать бизнес-правила любым удобным для нас способом:
Specification<City> criterion = new And<City>(
new And<City>(new MinPopulation(1000), new MaxPopulation(2000)),
new And<City>(new MinFemalePopulation(200), new MaxFemalePopulation(300))
);
(здесь MaxFemalePopulation
и MinFemalePopulation
должны быть определены)
Часто в спецификации используется шаблон Builder, который позволяет создавать спецификации с использованием «плавных интерфейсов»:
Specification<City> criterion = new SpecBuilder::from(new MinPopulation(1000))
.and(new MaxPopulation(2000))
.and(new MinFemalePopulation(200))
.and()
.build(MaxFemalePopulation(300));
Сортировка может быть выполнена после фильтрации, поскольку сортировки будет меньше:
Collections.sort(filtered);
Чтобы вызвать этот метод, нам нужен наш город для реализации Comparable
:
class City implements Comparable<City> {
private String name;
private int total;
private int men;
private int women;
public City(String name, int total, int men, int women) {
this.name = name;
this.total = total;
this.men = men;
this.women = women;
}
public String getName() { return name; }
public int getTotal() { return total; }
public int getMen() { return men; }
public int getWomen() { return women; }
@Override
public int compareTo(City city) {
return this.getName().compareTo(city.getName());
}
}