Javaにおけるフィルタリングパターン:動的フィルタによるデータ処理の効率化
別名
- フィルタ
- パイプとフィルタ
フィルタリングデザインパターンの目的
Javaにおけるフィルタリングデザインパターンは、動的でスケーラブルなフィルタリングソリューションを作成するために不可欠です。このパターンにより、一連のフィルタをデータオブジェクトに適用でき、データ処理の柔軟性とスケーラビリティが向上します。
実例を用いたフィルタリングパターンの詳細な説明
実例
ジャンル、著者、貸出状況など、フィルタ基準を動的に組み合わせるためにフィルタリングパターンを採用した図書館システムを考えてみましょう。このJavaパターンにより、システムの保守性とスケーラビリティが向上します。考えられるすべての基準の組み合わせごとに個別のメソッドを作成する代わりに、図書館システムはフィルタリングデザインパターンを採用します。各フィルタ基準はオブジェクトとしてカプセル化され、これらのフィルタオブジェクトは実行時に動的に組み合わせて、複雑なフィルタリングロジックを作成できます。たとえば、ユーザーは、貸出可能で、2010年以降に出版された書籍を、貸出状況フィルタと出版年フィルタを組み合わせることで検索できます。このアプローチにより、システムの柔軟性が高まり、保守が容易になります。新しいフィルタ基準を追加する場合でも、既存のコードを変更する必要がありません。
簡単に言うと
フィルタリングパターンは、コンテナのようなオブジェクトがそれ自身のフィルタリングされたバージョンを返すのに役立つデザインパターンです。
Javaにおけるフィルタリングパターンのプログラミング例
例として、Javaのマルウェア検出システムにフィルタリングデザインパターンを使用します。このシステムは、さまざまな基準に基づいて脅威をフィルタリングでき、パターンの柔軟性と動的な性質を示しています。設計においては、後で新しい脅威の種類を追加できることを考慮する必要があります。さらに、脅威検出システムが検出された脅威を異なる基準に基づいてフィルタリングできるという要件があります(対象システムは脅威のコンテナのようなオブジェクトとして機能します)。
脅威検出システムをモデル化するために、`Threat`インターフェースと`ThreatAwareSystem`インターフェースを導入します。
public interface Threat {
String name();
int id();
ThreatType type();
}
public interface ThreatAwareSystem {
String systemId();
List<? extends Threat> threats();
Filterer<? extends ThreatAwareSystem, ? extends Threat> filtered();
}
`Filterer`インターフェースのインスタンスを返す`filtered`メソッドに注目してください。これは次のように定義されています。
@FunctionalInterface
public interface Filterer<G, E> {
G by(Predicate<? super E> predicate);
}
`ThreatAwareSystem`の簡単な実装
public class SimpleThreatAwareSystem implements ThreatAwareSystem {
private final String systemId;
private final ImmutableList<Threat> issues;
public SimpleThreatAwareSystem(final String systemId, final List<Threat> issues) {
this.systemId = systemId;
this.issues = ImmutableList.copyOf(issues);
}
@Override
public String systemId() {
return systemId;
}
@Override
public List<? extends Threat> threats() {
return new ArrayList<>(issues);
}
@Override
public Filterer<? extends ThreatAwareSystem, ? extends Threat> filtered() {
return this::filteredGroup;
}
private ThreatAwareSystem filteredGroup(Predicate<? super Threat> predicate) {
return new SimpleThreatAwareSystem(this.systemId, filteredItems(predicate));
}
private List<Threat> filteredItems(Predicate<? super Threat> predicate) {
return this.issues.stream()
.filter(predicate)
.collect(Collectors.toList());
}
}
ここで、特定の脅威が現れる可能性を示す確率を追加する`Threat`インターフェースの新しいサブタイプを導入するとします。
public interface ProbableThreat extends Threat {
double probability();
}
確率付きの脅威を認識するシステムを表す新しいインターフェースを導入することもできます。
public interface ProbabilisticThreatAwareSystem extends ThreatAwareSystem {
@Override
List<? extends ProbableThreat> threats();
@Override
Filterer<? extends ProbabilisticThreatAwareSystem, ? extends ProbableThreat> filtered();
}
`ProbabilisticThreatAwareSystem`を`ProbableThreat`のプロパティでフィルタリングできるようになります。
public class SimpleProbabilisticThreatAwareSystem implements ProbabilisticThreatAwareSystem {
private final String systemId;
private final ImmutableList<ProbableThreat> threats;
public SimpleProbabilisticThreatAwareSystem(final String systemId, final List<ProbableThreat> threats) {
this.systemId = systemId;
this.threats = ImmutableList.copyOf(threats);
}
@Override
public String systemId() {
return systemId;
}
@Override
public List<? extends ProbableThreat> threats() {
return threats;
}
@Override
public Filterer<? extends ProbabilisticThreatAwareSystem, ? extends ProbableThreat> filtered() {
return this::filteredGroup;
}
private ProbabilisticThreatAwareSystem filteredGroup(final Predicate<? super ProbableThreat> predicate) {
return new SimpleProbabilisticThreatAwareSystem(this.systemId, filteredItems(predicate));
}
private List<ProbableThreat> filteredItems(final Predicate<? super ProbableThreat> predicate) {
return this.threats.stream()
.filter(predicate)
.collect(Collectors.toList());
}
}
それでは、フィルタリングパターンが実際どのように機能するかを示す完全な例を見てみましょう。
@Slf4j
public class App {
public static void main(String[] args) {
filteringSimpleThreats();
filteringSimpleProbableThreats();
}
private static void filteringSimpleProbableThreats() {
LOGGER.info("### Filtering ProbabilisticThreatAwareSystem by probability ###");
var trojanArcBomb = new SimpleProbableThreat("Trojan-ArcBomb", 1, ThreatType.TROJAN, 0.99);
var rootkit = new SimpleProbableThreat("Rootkit-Kernel", 2, ThreatType.ROOTKIT, 0.8);
List<ProbableThreat> probableThreats = List.of(trojanArcBomb, rootkit);
var probabilisticThreatAwareSystem =
new SimpleProbabilisticThreatAwareSystem("Sys-1", probableThreats);
LOGGER.info("Filtering ProbabilisticThreatAwareSystem. Initial : "
+ probabilisticThreatAwareSystem);
//Filtering using filterer
var filteredThreatAwareSystem = probabilisticThreatAwareSystem.filtered()
.by(probableThreat -> Double.compare(probableThreat.probability(), 0.99) == 0);
LOGGER.info("Filtered by probability = 0.99 : " + filteredThreatAwareSystem);
}
private static void filteringSimpleThreats() {
LOGGER.info("### Filtering ThreatAwareSystem by ThreatType ###");
var rootkit = new SimpleThreat(ThreatType.ROOTKIT, 1, "Simple-Rootkit");
var trojan = new SimpleThreat(ThreatType.TROJAN, 2, "Simple-Trojan");
List<Threat> threats = List.of(rootkit, trojan);
var threatAwareSystem = new SimpleThreatAwareSystem("Sys-1", threats);
LOGGER.info("Filtering ThreatAwareSystem. Initial : " + threatAwareSystem);
//Filtering using Filterer
var rootkitThreatAwareSystem = threatAwareSystem.filtered()
.by(threat -> threat.type() == ThreatType.ROOTKIT);
LOGGER.info("Filtered by threatType = ROOTKIT : " + rootkitThreatAwareSystem);
}
}
例を実行すると、次のコンソール出力が生成されます。
08:33:23.568 [main] INFO com.iluwatar.filterer.App -- ### Filtering ThreatAwareSystem by ThreatType ###
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)])
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)])
08:33:23.576 [main] INFO com.iluwatar.filterer.App -- ### Filtering ProbabilisticThreatAwareSystem by probability ###
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)])
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)])
Javaでフィルタリングパターンを使用する場合
- オブジェクトのコレクションの動的で柔軟なフィルタリングが必要な場合は、フィルタリングパターンを使用してください。
- このJavaデザインパターンは、フィルタリングロジックが頻繁に変更されるか、さまざまな方法で組み合わせる必要があるアプリケーションに最適です。
- フィルタリングロジックとコアビジネスロジックの分離が必要なシナリオに最適です。
フィルタリングパターンJavaチュートリアル
Javaにおけるフィルタリングパターンの実用例
- Apache Kafka StreamsなどのJavaのストリーム処理ライブラリは、このパターンを使用して複雑なデータ処理パイプラインを構築しています。
- 画像処理ソフトウェアは、画像に効果や変換を連続して適用するために、しばしばフィルタを使用します。
フィルタリングパターンのメリットとデメリット
メリット
- システムの他の部分に影響を与えることなく、異なるフィルタを追加または再配置できるため、柔軟性が向上します。
- フィルタを個別にテストできるため、テスト容易性が向上します。
- データ処理の各段階間の疎結合を促進します。
デメリット
- フィルタ間のデータの連続的な受け渡しによるパフォーマンスのオーバーヘッドが発生する可能性があります。
- フィルタの数が増えると複雑さが増し、保守性が低下する可能性があります。
関連するJavaデザインパターン
- 責任連鎖パターン:フィルタは、各フィルタが入力データを処理する方法と、チェーンに沿って渡すかどうかを決定する責任連鎖パターンの特殊な形式と見なすことができます。
- デコレータパターン:どちらも動的に動作を修正するという点でデコレータパターンに似ていますが、フィルタは責任の追加よりもデータ変換に重点を置いています。