Фильтр — это шаблон, который следует применять только в определенных случаях. В оригинальном посте я представил очень простой пример, призванный показать, как его применять. В этом посте я представлю гораздо более подробный пример, который также призван объяснить, когда и зачем его применять.
Вступление
Пост состоит из следующих 10 коротких шагов . На каждом этапе я представляю требования следующих двух типов:
- B- *: бизнес — требования (данные владельцем продукта → бесспорный)
- S- * : требования к решениям (вытекающие из выбора решений → спорные )
и я представляю модель Java, отвечающую требованиям, введенным до сих пор. Я делаю это, пока Filterer не Filterer в качестве предпочтительного решения.
Итак, позвольте мне взять вас в этом путешествии …
Шаг 1: детектор проблем
Требования № 1
Давайте предположим, что бизнес запрашивает алгоритм для выявления грамматических и орфографических проблем в английских текстах .
Например:
- текст: вы знаете это. → проблемы для обнаружения:
- миг (тип: орфография)
- Текст: я должен проиграть. → проблемы для обнаружения:
- отмечая (тип: орфография)
- потерять (тип: грамматика)
- Текст: я продолжал отмечать, что это свободно. → проблемы для выявления: ∅
Это наше первое деловое требование ( B-1 ).
Простейшей моделью встречи B-1 может быть:
- ввод : простой текст
- вывод : список проблем, где каждая проблема обеспечивает:
- смещения во входном тексте
- тип (грамматика / орфография)
Это наше первое требование к решению ( S-1 ).
Модель Java № 1
Мы можем моделировать S-1 как:
|
1
2
3
4
|
interface IssueDetector { // e.g. text: "You migth know it." List<Issue> detect(String text);} |
где:
|
1
2
3
4
5
|
interface Issue { int startOffset(); // e.g. 4 (start of "migth") int endOffset(); // e.g. 9 (end of "migth") IssueType type(); // e.g. SPELLING} |
|
1
|
enum IssueType { GRAMMAR, SPELLING } |
Это совершить 1 .
Шаг 2: Вероятность
Требования № 2
Однако было бы довольно сложно реализовать настоящий IssueDetector который работал бы таким детерминированным образом:
- проблема (вероятность P = 100% )
- не проблема (вероятность P = 0% )
Вместо этого IssueDetector должен быть скорее вероятностным :
- вероятная проблема (вероятность P =? )
Мы можем сохранить различие между проблемой и отсутствием, введя порог вероятности ( PT ):
- проблема (вероятность P ≥ PT ),
- не проблема (вероятность P <PT ).
Тем не менее, стоит адаптировать модель для сохранения вероятности ( P ) — это полезно, например, при рендеринге (более высокая вероятность → более заметный рендеринг).
Подводя итог, наши дополнительные требования решения :
- S-2 : вероятность возникновения проблемы поддержки ( P );
- S-3 : порог вероятности поддержки ( PT ).
Модель Java № 2
Мы можем встретить S-2 , добавив probability() к Issue :
|
1
2
3
4
|
interface Issue { // ... double probability();} |
Мы можем встретиться с S-3 , добавив probabilityThreshold IssueDetector в IssueDetector :
|
1
2
3
|
interface IssueDetector { List<Issue> detect(String text, double probabilityThreshold);} |
Это совершить 2 .
Шаг 3: Вероятная проблема
Требования № 3
Предположим, что бизнес требует :
- B-3 : Проверьте все детекторы проблем, используя проверенные тексты английского лингвиста (= нет вероятностей).
Такой корректный текст (или: контрольный пример ) может быть определен как:
- текст, например, вы должны знать это.
- ожидаемые проблемы, например
- shuold (тип: правописание)
Итак, наше решение :
- S-4 : Поддержка ожидаемых проблем (= нет вероятности).
Модель Java № 3
Мы можем встретить S-4 , извлекая подынтерфейс ( ProbableIssue ):
|
1
2
3
|
interface ProbableIssue extends Issue { double probability();} |
и возвращая ProbableIssue из IssueDetector :
|
1
2
3
|
interface IssueDetector { List<ProbableIssue> detect(...);} |
Это совершить 3 .
Шаг 4: Выпуск текста
Требования № 4
Предположим, что:
- Все тестовые примеры определены внешне (например, в файлах XML);
- Мы хотим создать параметризованный тест JUnit, где параметры — это тестовые случаи, представленные в виде
Stream.
Как правило, тестовый пример представляет собой нечто, что мы могли бы назвать проблемным текстом (текст + его проблемы).
Чтобы избежать моделирования проблемного текста как Map.Entry<String, List<Issue>> (который является расплывчатым и означает недостаточную абстракцию ), давайте введем еще одно требование к решению :
- S-5 : Поддержка текстовых сообщений.
Модель Java № 4
Мы можем моделировать S-5 как:
|
1
2
3
4
|
interface IssueWiseText { String text(); // e.g. "You migth know it." List<Issue> issues(); // e.g. ["migth"]} |
Это позволяет нам определить Stream тестовых случаев просто как
-
Stream<IssueWiseText>
вместо
-
Stream<Map.Entry<String, List<Issue>>>.
Это совершить 4 .
Шаг 5: ожидаемое покрытие
Требования № 5
Предположим, что бизнес требует :
- B-4 : сообщить об ожидаемом освещении проблемы для потока тестовых случаев ;
где освещение вопроса — для простоты — определяется как:
общая длина вопроса
─────────────
общая длина текста
В действительности, освещение проблемы может представлять собой очень сложную бизнес-логику .
Модель Java № 5
Мы можем обработать B-4 с помощью метода на основе Collector :
|
1
2
3
|
static double issueCoverage(Stream<? extends IssueWiseText> textStream) { return textStream.collect(IssueCoverage.collector());} |
Collector основан на Accumulator имеющем два изменяемых поля:
|
1
2
|
int totalIssueLength = 0;int totalTextLength = 0; |
который для каждого IssueWiseText мы увеличиваем:
|
1
2
|
totalIssueLength += issueWiseText.issues().stream().mapToInt(Issue::length).sum();totalTextLength += issueWiseText.text().length(); |
а затем мы рассчитываем покрытие проблемы как:
|
1
|
(double) totalIssueLength / totalTextLength |
Это совершить 5 .
Шаг 6: Полученное покрытие
Требования № 6
Предположим, что бизнес требует :
- B-5 : Сообщите о полученном освещении проблемы для всего набора тестов.
где «полученный» означает «рассчитанный с использованием обнаруженных проблем». Теперь все становится интересным!
Прежде всего, поскольку IssueCoverage представляет бизнес-логику , мы не должны дублировать ее:
- S-6 : Повторно использовать код покрытия проблемы.
Во-вторых, так как метод принимает Stream<? extends IssueWiseText> Stream<? extends IssueWiseText> , нам нужно смоделировать IssueWiseText для ProbableIssue s:
- S-7 : Поддержка вероятностных текстов, касающихся вопросов.
Я вижу только два варианта:
- Параметризация :
IssueWiseText<I extends Issue>; - Подтип :
ProbabilisticIssueWiseText extends IssueWiseText.
Параметрическая модель Java № 6
Параметрическая модель S-7 проста — нам нужно <I extends Issue> ( параметр ограниченного типа ) в IssueWiseText :
|
1
2
3
4
|
interface IssueWiseText<I extends Issue> { String text(); List<I> issues();} |
У этой модели есть недостатки (например, стирание типов ), но она лаконична.
Мы также можем адаптировать IssueDetector для возврата IssueWiseText<ProbableIssue> .
Более того, наш Stream тестовых случаев может превратиться в Stream<IssueWiseText<Issue>> (хотя IssueWiseText<Issue> является довольно спорным).
Это совершить 6а .
Подтип Java-модель № 6
Другой вариант — выбрать подтип (который имеет свои недостатки, самым большим из которых может быть дублирование ).
Модель подтипа S-7 использует ковариацию возвращаемого типа :
|
1
2
3
4
|
interface ProbabilisticIssueWiseText extends IssueWiseText { @Override List<? extends ProbableIssue> issues();} |
где issues() в IssueWiseText должны стать верхними IssueWiseText ( List<? extends Issue> ).
Мы также можем адаптировать IssueDetector для возврата ProbabilisticIssueWiseText .
Это совершить 6b .
Шаг 7: Фильтрация по типу проблемы
Требования № 7
Предположим, что бизнес требует :
- B-6 : Сообщить о проблеме по типу проблемы .
Мы могли бы поддержать это, приняв дополнительный параметр типа Predicate <? super Issue> Predicate <? super Issue> (параметр IssueType , будет слишком узким).
Однако его прямая поддержка в IssueCoverage усложнит бизнес-логику ( commit 7a ‘ ). Вместо этого мы предпочли бы IssueWiseText отфильтрованные экземпляры IssueWiseText в IssueCoverage .
Как мы делаем фильтрацию? Выполнение этого «вручную» (вызов new самостоятельно) приведет к ненужной связи с реализациями (мы даже еще не знаем их). Вот почему мы позволим IssueWiseText выполнять фильтрацию (я чувствую, что эта логика здесь):
- S-8 : Поддержка фильтрации по
IssueWiseTextвIssueWiseText.
Другими словами, мы хотим иметь возможность сказать:
Другими словами, мы хотим иметь возможность сказать:
Эй
IssueWiseText, отфильтруйте себя поIssue!
Параметрическая модель Java № 7
В параметрической модели мы добавляем следующий filtered метод в IssueWiseText<I>
|
1
|
IssueWiseText<I> filtered(Predicate<? super I> issueFilter); |
Это позволяет нам встретить B-6 как:
|
1
2
3
|
return textStream .map(text -> text.filtered(issue -> issue.type() == issueType)) .collect(IssueCoverage.collector()); |
Это совершить 7а .
Подтип Java Модель № 7
В модели подтипов мы также добавляем filtered метод (очень похожий на приведенный выше):
|
1
|
IssueWiseText filtered(Predicate<? super Issue> issueFilter); |
Это позволяет нам встретить B-6 так же, как и выше.
Это совершить 7b .
Шаг 8: Фильтрация по вероятности
Требования № 8
Предположим, что бизнес требует :
- B-7 : Сообщить о покрытии проблемы с минимальной вероятностью .
Другими словами, бизнес хочет знать, как распределение вероятностей влияет на охват проблемы.
Теперь мы не хотим запускать IssueDetector с большим количеством различных порогов вероятности ( PT ), потому что это будет очень неэффективно. Вместо этого мы запустим его только один раз (с PT = 0 ), а затем продолжим отбрасывать проблемы с наименьшей вероятностью для пересчета охвата проблемы.
Тем не менее, чтобы иметь возможность фильтровать по вероятностям, нам необходимо:
- S-9 : Поддержка фильтрации по
ProbableIssueв вероятностном проблемном тексте.
Параметрическая модель Java № 8
В параметрической модели нам не нужно ничего менять. Мы можем встретить B-7 как:
|
1
2
3
|
return textStream .map(text -> text.filtered(issue -> issue.probability() >= minProbability)) .collect(IssueCoverage.collector()); |
Это совершить 8а .
Подтип Java-модель № 8
В модели подтипов это сложнее, потому что нам нужен дополнительный метод в ProbabilisticIssueWiseText :
|
1
|
ProbabilisticIssueWiseText filteredProbabilistic(Predicate<? super ProbableIssue> issueFilter); |
что позволяет нам встретить B-7 как:
|
1
2
3
|
return textStream .map(text -> text.filteredProbabilistic(issue -> issue.probability() >= minProbability)) .collect(IssueCoverage.collector()); |
Это совершить 8b .
Для меня этот дополнительный метод в ProbabilisticIssueWiseText довольно тревожный, хотя (см. Здесь ). Вот почему я предлагаю …
Шаг 9: Фильтр
Требования № 9
Поскольку регулярная фильтрация в модели подтипов настолько «неоднородна», давайте сделаем ее равномерной:
- S-10 : Поддержка равномерной фильтрации в модели подтипов текстового вопроса.
Другими словами, мы хотим иметь возможность сказать:
Привет
ProbabilisticIssueWiseText, отфильтруйте себя поProbableIssue(но так же, какIssueWiseTextфильтрует себя поIssue)!
Насколько мне известно, это может быть достигнуто только с помощью шаблона Filterer .
Подтип Java-модель № 9
Поэтому мы применяем универсальный Filterer к IssueWiseText :
|
1
|
Filterer<? extends IssueWiseText, ? extends Issue> filtered(); |
и в ProbablisticIssueWiseText :
|
1
2
|
@OverrideFilterer<? extends ProbabilisticIssueWiseText, ? extends ProbableIssue> filtered(); |
Теперь мы можем фильтровать равномерно, вызывая:
|
1
|
text.filtered().by(issue -> ...) |
Это совершить 9 .
Шаг 10: Время обнаружения
К этому времени вы должны задаться вопросом, почему я беспокоюсь о модели подтипов, если параметрическая модель намного проще.
Итак, в последний раз, давайте предположим, что бизнес требует :
- B-8 : Время обнаружения отчета (= время, необходимое для обнаружения всех проблем в данном тексте).
Параметрическая модель Java № 10
Я вижу только два способа включения B-8 в параметрическую модель: 1) композиция, 2) подтип.
Композиция для параметрической модели Java № 10
Наносить композицию легко. Мы представляем IssueDetectionResult :
|
1
2
3
4
|
interface IssueDetectionResult { IssueWiseText<ProbableIssue> probabilisticIssueWiseText(); Duration detectionTime();} |
и измените IssueDetector чтобы вернуть его.
Это совершить 10а .
Подтип для параметрической модели Java № 10
Применение подтипов требует немного больше работы. Нам нужно добавить ProbabilisticIssueWiseText<I> *
|
1
2
3
4
|
interface ProbabilisticIssueWiseText<I extends ProbableIssue> extends IssueWiseText<I> { Duration detectionTime(); // ...} |
и измените IssueDetector чтобы он возвращал ProbabilisticIssueWiseText<?> .
Это совершить 10а ‘ .
* Обратите внимание, что я оставил <I> на ProbabilisticIssueWiseText чтобы не связывать параметризацию с подтипами опасным способом .
Подтип Java Модель № 10
С моделью с чисто подтипами включить B-8 очень легко. Мы просто добавляем detectionTime() в ProbabilisticIssueAwareText :
|
1
2
3
4
|
interface ProbabilisticIssueWiseText extends IssueWiseText { Duration detectionTime(); // ...} |
Это совершить 10b .
Выводы
Там нет времени, чтобы вдаваться в детали (пост уже намного дольше, чем я ожидал).
Тем не менее, я предпочитаю чистый Filterer (и, следовательно, Filterer ), чем другие решения, потому что:
- Параметризация с композицией оставляет меня без общего супертипа (в некоторых случаях это проблема);
- Параметризация с подтипами имеет слишком много степеней свободы.
Под «слишком многими степенями свободы» я имею в виду, что мне нужно только:
-
IssueAwareText<?> -
ProbabilisticIssueAwareText<?> -
IssueAwareText<Issue>(спорный)
но в коде я тоже столкнусь (говорю из опыта!):
-
IssueAwareText<? extends Issue>IssueAwareText<? extends Issue>(избыточная верхняя граница) -
IssueAwareText<ProbableIssue> -
IssueAwareText<? extends ProbableIssue>IssueAwareText<? extends ProbableIssue>(почему неProbabilisticIssueAwareText<?>?) -
ProbabilisticIssueAwareText<? extends ProbableIssue>ProbabilisticIssueAwareText<? extends ProbableIssue>(избыточная верхняя граница) -
ProbabilisticIssueAwareText<ProbableIssue>
так что это слишком запутанно для меня. Но если вы действительно интересуетесь этой темой, посмотрите « Сложный подтип против параметризации» (но будьте осторожны — он даже длиннее, чем этот пост!).
Спасибо за чтение!
|
Опубликовано на Java Code Geeks с разрешения Томаша Линковски, партнера нашей программы JCG. Смотрите оригинальную статью здесь: Узор Filterer за 10 шагов Мнения, высказанные участниками Java Code Geeks, являются их собственными. |