Последнее изменение: 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
(по убыванию и возрастанию, соответственно). Таким образом, первыми
будут применяться фильтры, которые с большей вероятностью дадут результат, определяющий результат всей операции.