Последнее изменение: 1 сентября 2007г.
Библиотека фильтрации
Честно сказать, появилась эта библиотека в большой степени случайно. Я в очередной раз (кажется, уже в третий) столкнулся с необходимостью иметь реализацию арифметики фильтров, в результате чего решил выделить, наконец, этот код в отдельный проект.
По большому счету, библиотека тривиальна. Она содержит определение фильтра, фильтруемого объекта и четырех операций над
фильтрами – AND, OR, NOT и XOR. Эти операции позволяют создать логику
любой сложности. В принципе, можно было бы обойтись и без XOR, она добавлена для удобства.
Это описание делится на две части:
- Описание библиотеки – основные классы и интерфейсы
- Исходный код – исходный код библиотеки и инструкции по сборке
Сразу хочу предупредить. Библиотека написана с использованием generics, т.е. может использоваться только с Java 5.0+.
* * *
Описание библиотеки
Итак, посмотрим на библиотеку поближе.
Фильтруемый объект определяется интерфейсом ru.skipy.filtering.Filterable. Этот интерфейс пустой, по сути –
маркер.
Функциональность фильтра определяется интерфейсом ru.skipy.filtering.Filter. Он приведен ниже (все
комментарии опущены, полный вариант есть в исходном коде):
package ru.skipy.filtering;
public interface Filter <T extends Filterable> {
public boolean accept(T element);
public String getName();
public String getDescription();
public String getHumanreadableRule();
}
Основной метод интерфейса – accept. Полагаю, комментарии тут излишни, его назначение более чем понятно.
Кроме него в интерфейсе есть еще три метода. getName и getDescription в большой степени
предназначены для использования в интерфейсе пользователя. Они возвращают, соответственно, имя и описание фильтра. Последний
метод, getHumanreadableRule, должен возвращать правило фильтрации в виде, нонятном человеку. Этот метод
может быть очень полезен при создании сложных фильтров с помощью операций – классы, реализующие операции, используют его
для создания полного правила фильтрации. Реализацию и применение всех этих методов можно найти в примере, который находится
вместе с библиотекой в виде исходников.
Для тех, кто не знаком с generics, пара слов об объявлении интерфейса. Выражение <T extends
Filterable> означает возможность параметризации. Параметром-типом является тип T, который должен быть
наследником интерфейса Filterable. Как вы можете заметить, тот же тип фигурирует и в объявлении метода
accept. Это означает, что реализующий этот интерфейс разработчик может объявить действительный тип, тем самым получив
его в методе accept:
public class MyData implements Filterable{
//...
}
public class MyFilter implements Filter<MyData>{
public boolean accept(MyData element){
// ...
}
// ...
}
Все проверки соответствия типов осуществляются на стадии компиляции. Благодаря этому существует возможность
избавиться от большого количества проверок instanceof и приведений типов, одновременно повысив
читаемость и надежность кода.
Перейдем теперь к операциям над фильтрами. Как я уже упоминал, их реализовано четыре – AND, OR,
NOT и XOR. В принципе, XOR можно было не реализовывать, ибо
a xor b = (a or b) and not(a and b)
Однако на практике это означало бы создание сложного фильтра (4-х экземпляров интерфейса Filter), при том,
что реализация его тривиальна.
Самая простая операция – NOT. Она реализована классом ru.skipy.filtering.operations.NotOp.
Конструктор этого класса принимает единственный параметр – фильтр, значение которого он будет инвертировать.
Далее по сложности – операция XOR. Она реализована классом ru.skipy.filtering.operations.XorOp.
В конструкторе у нее два параметра – фильтры, над которыми будет произведена операция. Хочу напомнить:
xor возвращает false при равных аргументах и true при неравных.
Далее идут операции AND и OR (соответственно, ru.skipy.filtering.operations.AndOp
и ru.skipy.filtering.operations.OrOp). Эти операции могут работать одновременно с несколькими фильтрами, причем
все фильтры связаны одним и тем же отношением операции.
Каждый из реализующих операции классов имеет два конструктора. Один принимает набор фильтров
(java.util.List<Filter<T>>), второй – два фильтра. Набор фильтров не может быть пустым, фильтры
не могут быть равны null. Впрочем, все это есть в javadoc.
Фильтры применяются в порядке их указания в наборе (либо первый, потом второй фильтры при использовании конструктора
с двумя фильтрами). Важно помнить, что фильтры в целях оптимизации производительности применяются до получения
определенности. Иначе говоря, если какой-либо из вложенных фильтров в операции AND вернул false,
то результат уже определен, соответственно, остальные фильтры не применяются. Аналогично действует и возврат вложенным
фильтром значения true в операции OR.
Собственно, это всё. Пример реализации и применения фильтров есть в тесте, который находится в архиве вместе с исходным кодом.
* * *
Исходный код
Исходный код библиотеки находится здесь – filtering.zip. Он поставляется вместе с
build.xml для ant. По умолчанию ant компилирует библиотеку и создает файл
filtering.jar в директории lib. Документация генерируется в директории doc с помощью
команды ant javadoc, тест запускается с помощью команды ant run-test. Тест весьма и весьма
тривиален, он лишь демонстрирует создание и использование сложного фильтра.
Обратите внимание, что для корректной работы ant необходимо правильно установить переменную
JAVA_HOME. Она должна указывать на директорю, в которой установлен J2SDK 5.0.
Всё. Пользуйтесь!
В планах, возможно, следующее. Для большей оптимизации производительности я планирую ввести в фильтрах весовой
коэффициент – вероятность выдать true. Эту вероятность могут установить только непосредственно разработчики
фильтров, на основе знания предметной области. Применяться этот параметр фильтра будет так: по нему будут отсортированы
фильтры в операциях OR и AND (по убыванию и возрастанию, соответственно). Таким образом, первыми
будут применяться фильтры, которые с большей вероятностью дадут результат, определяющий результат всей операции.



skipy.ru)