Skip to content

Commit 8985c33

Browse files
committed
docs: update filterer
1 parent 3035f40 commit 8985c33

File tree

2 files changed

+73
-38
lines changed

2 files changed

+73
-38
lines changed

chain-of-responsibility/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
---
2-
title: Chain of responsibility
2+
title: Chain of Responsibility
33
category: Behavioral
44
language: en
55
tag:

filterer/README.md

Lines changed: 72 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ tag:
66
- Data processing
77
- Data transformation
88
- Decoupling
9+
- Functional decomposition
10+
- Object composition
911
- Performance
1012
- Runtime
1113
---
@@ -21,49 +23,44 @@ The Filterer pattern aims to apply a series of filters to data objects, where ea
2123

2224
## Explanation
2325

24-
Real world example
26+
Real-world example
2527

26-
> We are designing a threat (malware) detection software which can analyze target systems for threats that are present in it. In the design we have to take into consideration that new Threat types can be added later. Additionally, there is a requirement that the threat detection system can filter the detected threats based on different criteria (the target system acts as container-like object for threats).
28+
> Imagine a library that needs to filter books based on different criteria such as genre, author, publication year, or availability. Instead of writing separate methods for each possible combination of criteria, the library system employs the Filterer design pattern. Each filter criterion is encapsulated as an object, and these filter objects can be combined dynamically at runtime to create complex filtering logic. For example, a user can search for books that are both available and published after 2010 by combining the availability filter and the publication year filter. This approach makes the system more flexible and easier to maintain, as new filtering criteria can be added without modifying existing code.
2729
2830
In plain words
2931

3032
> Filterer pattern is a design pattern that helps container-like objects return filtered versions of themselves.
3133
3234
**Programmatic Example**
3335

34-
To model the threat detection example presented above we introduce `Threat` and `ThreatAwareSystem` interfaces.
36+
We are designing a threat (malware) detection software which can analyze target systems for threats that are present in it. In the design we have to take into consideration that new Threat types can be added later. Additionally, there is a requirement that the threat detection system can filter the detected threats based on different criteria (the target system acts as container-like object for threats).
37+
38+
To model the threat detection system, we introduce `Threat` and `ThreatAwareSystem` interfaces.
3539

3640
```java
3741
public interface Threat {
3842
String name();
39-
4043
int id();
41-
4244
ThreatType type();
4345
}
4446

4547
public interface ThreatAwareSystem {
4648
String systemId();
47-
4849
List<? extends Threat> threats();
49-
5050
Filterer<? extends ThreatAwareSystem, ? extends Threat> filtered();
5151
}
5252
```
5353

5454
Notice the `filtered` method that returns instance of `Filterer` interface which is defined as:
5555

5656
```java
57-
5857
@FunctionalInterface
5958
public interface Filterer<G, E> {
6059
G by(Predicate<? super E> predicate);
6160
}
6261
```
6362

64-
It is used to fulfill the requirement for system to be able to filter itself based on threat properties. The container-like object (`ThreatAwareSystem` in our case) needs to have a method that returns an instance of `Filterer`. This helper interface gives ability to covariantly specify a lower bound of contravariant `Predicate` in the subinterfaces of interfaces representing the container-like objects.
65-
66-
In our example we will be able to pass a predicate that takes `? extends Threat` object and return `? extends ThreatAwareSystem` from `Filtered::by` method. A simple implementation of `ThreatAwareSystem`:
63+
A simple implementation of `ThreatAwareSystem`:
6764

6865
```java
6966
public class SimpleThreatAwareSystem implements ThreatAwareSystem {
@@ -103,8 +100,6 @@ public class SimpleThreatAwareSystem implements ThreatAwareSystem {
103100
}
104101
```
105102

106-
The `filtered` method is overridden to filter the threats list by given predicate.
107-
108103
Now if we introduce a new subtype of `Threat` interface that adds probability with which given threat can appear:
109104

110105
```java
@@ -115,17 +110,16 @@ public interface ProbableThreat extends Threat {
115110

116111
We can also introduce a new interface that represents a system that is aware of threats with their probabilities:
117112

118-
````java
113+
```java
119114
public interface ProbabilisticThreatAwareSystem extends ThreatAwareSystem {
120115
@Override
121116
List<? extends ProbableThreat> threats();
122-
123117
@Override
124118
Filterer<? extends ProbabilisticThreatAwareSystem, ? extends ProbableThreat> filtered();
125119
}
126-
````
120+
```
127121

128-
Notice how we override the `filtered` method in `ProbabilisticThreatAwareSystem` and specify different return covariant type by specifying different generic types. Our interfaces are clean and not cluttered by default implementations. We will be able to filter `ProbabilisticThreatAwareSystem` by `ProbableThreat` properties:
122+
We will be able to filter `ProbabilisticThreatAwareSystem` by `ProbableThreat` properties:
129123

130124
```java
131125
public class SimpleProbabilisticThreatAwareSystem implements ProbabilisticThreatAwareSystem {
@@ -165,28 +159,67 @@ public class SimpleProbabilisticThreatAwareSystem implements ProbabilisticThreat
165159
}
166160
```
167161

168-
Now if we want filter `ThreatAwareSystem` by threat type we can do:
162+
Now let's see the full example in action showing how the filterer pattern works in practice.
169163

170164
```java
171-
Threat rootkit=new SimpleThreat(ThreatType.ROOTKIT, 1, "Simple-Rootkit");
172-
Threat trojan=new SimpleThreat(ThreatType.TROJAN, 2, "Simple-Trojan");
173-
List<Threat> threats=List.of(rootkit, trojan);
165+
@Slf4j
166+
public class App {
174167

175-
ThreatAwareSystem threatAwareSystem=new SimpleThreatAwareSystem("System-1", threats);
168+
public static void main(String[] args) {
169+
filteringSimpleThreats();
170+
filteringSimpleProbableThreats();
171+
}
176172

177-
ThreatAwareSystem rootkitThreatAwareSystem=threatAwareSystem.filtered().by(threat -> threat.type() == ThreatType.ROOTKIT);
178-
```
173+
private static void filteringSimpleProbableThreats() {
174+
LOGGER.info("### Filtering ProbabilisticThreatAwareSystem by probability ###");
179175

180-
Or if we want to filter `ProbabilisticThreatAwareSystem`:
176+
var trojanArcBomb = new SimpleProbableThreat("Trojan-ArcBomb", 1, ThreatType.TROJAN, 0.99);
177+
var rootkit = new SimpleProbableThreat("Rootkit-Kernel", 2, ThreatType.ROOTKIT, 0.8);
181178

182-
```java
183-
ProbableThreat malwareTroyan=new SimpleProbableThreat("Troyan-ArcBomb", 1, ThreatType.TROJAN, 0.99);
184-
ProbableThreat rootkit = new SimpleProbableThreat("Rootkit-System", 2, ThreatType.ROOTKIT, 0.8);
185-
List<ProbableThreat> probableThreats = List.of(malwareTroyan, rootkit);
179+
List<ProbableThreat> probableThreats = List.of(trojanArcBomb, rootkit);
186180

187-
ProbabilisticThreatAwareSystem simpleProbabilisticThreatAwareSystem = new SimpleProbabilisticThreatAwareSystem("System-1", probableThreats);
181+
var probabilisticThreatAwareSystem =
182+
new SimpleProbabilisticThreatAwareSystem("Sys-1", probableThreats);
188183

189-
ProbabilisticThreatAwareSystem filtered = simpleProbabilisticThreatAwareSystem.filtered().by(probableThreat -> Double.compare(probableThreat.probability(), 0.99) == 0);
184+
LOGGER.info("Filtering ProbabilisticThreatAwareSystem. Initial : "
185+
+ probabilisticThreatAwareSystem);
186+
187+
//Filtering using filterer
188+
var filteredThreatAwareSystem = probabilisticThreatAwareSystem.filtered()
189+
.by(probableThreat -> Double.compare(probableThreat.probability(), 0.99) == 0);
190+
191+
LOGGER.info("Filtered by probability = 0.99 : " + filteredThreatAwareSystem);
192+
}
193+
194+
private static void filteringSimpleThreats() {
195+
LOGGER.info("### Filtering ThreatAwareSystem by ThreatType ###");
196+
197+
var rootkit = new SimpleThreat(ThreatType.ROOTKIT, 1, "Simple-Rootkit");
198+
var trojan = new SimpleThreat(ThreatType.TROJAN, 2, "Simple-Trojan");
199+
List<Threat> threats = List.of(rootkit, trojan);
200+
201+
var threatAwareSystem = new SimpleThreatAwareSystem("Sys-1", threats);
202+
203+
LOGGER.info("Filtering ThreatAwareSystem. Initial : " + threatAwareSystem);
204+
205+
//Filtering using Filterer
206+
var rootkitThreatAwareSystem = threatAwareSystem.filtered()
207+
.by(threat -> threat.type() == ThreatType.ROOTKIT);
208+
209+
LOGGER.info("Filtered by threatType = ROOTKIT : " + rootkitThreatAwareSystem);
210+
}
211+
}
212+
```
213+
214+
Running the example produces the following console output.
215+
216+
```
217+
08:33:23.568 [main] INFO com.iluwatar.filterer.App -- ### Filtering ThreatAwareSystem by ThreatType ###
218+
08:33:23.574 [main] INFO com.iluwatar.filterer.App -- Filtering ThreatAwareSystem. Initial : SimpleThreatAwareSystem(systemId=Sys-1, issues=[SimpleThreat(threatType=ROOTKIT, id=1, name=Simple-Rootkit), SimpleThreat(threatType=TROJAN, id=2, name=Simple-Trojan)])
219+
08:33:23.576 [main] INFO com.iluwatar.filterer.App -- Filtered by threatType = ROOTKIT : SimpleThreatAwareSystem(systemId=Sys-1, issues=[SimpleThreat(threatType=ROOTKIT, id=1, name=Simple-Rootkit)])
220+
08:33:23.576 [main] INFO com.iluwatar.filterer.App -- ### Filtering ProbabilisticThreatAwareSystem by probability ###
221+
08:33:23.581 [main] INFO com.iluwatar.filterer.App -- Filtering ProbabilisticThreatAwareSystem. Initial : SimpleProbabilisticThreatAwareSystem(systemId=Sys-1, threats=[SimpleProbableThreat{probability=0.99} SimpleThreat(threatType=TROJAN, id=1, name=Trojan-ArcBomb), SimpleProbableThreat{probability=0.8} SimpleThreat(threatType=ROOTKIT, id=2, name=Rootkit-Kernel)])
222+
08:33:23.581 [main] INFO com.iluwatar.filterer.App -- Filtered by probability = 0.99 : SimpleProbabilisticThreatAwareSystem(systemId=Sys-1, threats=[SimpleProbableThreat{probability=0.99} SimpleThreat(threatType=TROJAN, id=1, name=Trojan-ArcBomb)])
190223
```
191224

192225
## Class diagram
@@ -195,12 +228,14 @@ ProbabilisticThreatAwareSystem filtered = simpleProbabilisticThreatAwareSystem.f
195228

196229
## Applicability
197230

198-
This pattern is useful in scenarios where data needs to be processed in discrete steps, and each step's output is the input for the next step. Common in stream processing, audio/video processing pipelines, or any data processing applications requiring staged transformations.
231+
* Use when you need to filter a collection of objects dynamically based on different criteria.
232+
* Suitable for applications where filtering logic changes frequently or needs to be combined in various ways.
233+
* Ideal for scenarios requiring separation of filtering logic from the core business logic.
199234

200235
## Tutorials
201236

202-
* [Article about Filterer pattern posted on its author's blog](https://blog.tlinkowski.pl/2018/filterer-pattern/)
203-
* [Application of Filterer pattern in domain of text analysis](https://www.javacodegeeks.com/2019/02/filterer-pattern-10-steps.html)
237+
* [Filterer Pattern (Tomasz Linkowski)](https://blog.tlinkowski.pl/2018/filterer-pattern/)
238+
* [Filterer Pattern in 10 Steps (Java Code Geeks)](https://www.javacodegeeks.com/2019/02/filterer-pattern-10-steps.html)
204239

205240
## Known Uses
206241

@@ -215,15 +250,15 @@ Benefits:
215250
* Enhances testability, as filters can be tested independently.
216251
* Promotes loose coupling between the stages of data processing.
217252

218-
## Trade-offs:
253+
Trade-offs:
219254

220255
* Potential performance overhead from continuous data passing between filters.
221256
* Complexity can increase with the number of filters, potentially affecting maintainability.
222257

223258
## Related Patterns
224259

225-
* Chain of Responsibility: Filters can be seen as a specialized form of the Chain of Responsibility, where each filter decides if and how to process the input data and whether to pass it along the chain.
226-
* Decorator: Similar to Decorator in that both modify behavior dynamically; however, filters focus more on data transformation than on adding responsibilities.
260+
* [Chain of Responsibility](https://java-design-patterns.com/patterns/chain-of-responsibility/): Filters can be seen as a specialized form of the Chain of Responsibility, where each filter decides if and how to process the input data and whether to pass it along the chain.
261+
* [Decorator](https://java-design-patterns.com/patterns/decorator/): Similar to Decorator in that both modify behavior dynamically; however, filters focus more on data transformation than on adding responsibilities.
227262

228263
## Credits
229264

0 commit comments

Comments
 (0)