Pipeline and Filters Patternについて勉強してみる

はいどーも人生のパイプがない無職です。
本日はPipeline and Filters Patternについて勉強してみようと思いますよ。
参考にするサイトは以下のものです。
https://www.codeproject.com/Articles/1094513/%2FArticles%2F1094513%2FPipeline-and-Filters-Pattern-using-Csharp

ちなみにですが、この記事を書きながらコードも書いているので、特に前半部分は僕が実際にまだ理解していないことを想像で書いてることもあります。
その点ご了承ください。

どういうパターンやねん

Introductionで貼られている画像を見た感じ、あるObjectをPipelineに通し、変換(あるいはフィルタリング)するパターンのように思えます。
C#でもLINQを使ってよく変換はすると思います。
それと似たようなパターンでしょうかね。

何の役に立つのか

恐らくですが、Objectの変換の作業をおこなうとき、PipelineのインスタンスはFilterを付け替えることができるのでしょう。
Filterを付け替えられるということは、同じObjectに対して別のPipelineを渡すと、別のObjectが結果として返ってくることを意味します。
また、Filterを簡単に付け替えられるということは、例えばフィルターが3つしかないとしても、組み合わせとしてはけっこーな数が考えられます。
特に「Filterを通す順番によって最終結果が変わる」場合、(いいか悪いかはさておき)可能な組み合わせは爆発的に増えます。

日記を作ってPipes and Filters Patternを適用してみる

今回はサンプルとして日記を作ってみようと思います。
そして、この日記にPipes and Filters Patternを適用しようと、そういうわけなんですね。
というわけでまずは1日分の日記の塊をクラスとして表してみます。

注*さっさとコード全体を見たい方はGistをどうぞ

特に解説することはないですね。
プロパティしか持っていません。

さて参考サイトは次に、この一つ一つの日記をまとめるクラスを作成しています。
僕も真似してみます。

注*参考サイトは日記を作っているわけではなく、AgentStatusなるものにPipes and Filters Patternを適用していこうとしています。ただAgentStatusとか言われてもあんまり馴染みがないので、ここでは日記を作ることにしました。

はいこんな感じですね。こちらも特に解説することはありません。

なお、意味のわからない文字列の部分は実際に暗号化しているわけでなく、lz-stringで圧縮しただけです。
元の文字列は「世界は光に包まれている。小鳥たちは愛を交わし、太陽は祝福の賛美歌を奏でている。だのに僕の心はどんよりと曇っていた。」となっています。
いやあ、この日記を書いた人はきっと詩人なんですね。

インターフェイスの作成

さてここまでで下準備ができました。
ここからが本題ですね。
Pipeline and Filtersパターンをざっと眺めておきたいので、まずは参考サイトのPipeline and Filters Class Diagramをご覧ください。

見ると、IFilter<T>インターフェイスがあります。
これを実装する具象型のConcreteFilterクラスもありますね。
似たような感じで、抽象クラスのPipeline<T>クラスがあります。
その具象型であるConcretePipelineクラスはIFilter<T>インターフェイスを実装しているクラス(ConcreteFilterクラス)を所持することになります。

なんだか形が見えてきましたね(実装すべきメソッド等については後で見ます)。
まずはIFilter<T>インターフェイスを作成してみましょう。
こんな感じです。

ワオシンプル!
シンプルすぎて、これだけじゃ何をしたいのかよくわかりませんね(一応参考サイトには意図がコメントで書かれていますが、やっぱり具象型を見た方が早い気がします)。
このインターフェイスで具体的に何をするのかは、具象型を作成するときに見ていきたいと思います。

抽象クラスを作成する

次は抽象クラスを作成します。
Pipeline<T>クラスですね。
こんな感じ。

ここは少し実装を丁寧に見ていきます。
_filtersフィールドはパイプライン内のフィルタのリストを意図しているようです。
読み取り専用になっているのは、インスタンスを差し替えられないようにするためでしょうね。
また、protectedになっているので、継承先のクラスでしかこのフィールドは見えません(つまり具象クラスでアクセスしてもらう意図があると思われる)。

で、フィルタのリストはどうやって登録するかと言えば、その名の通りのRegisterメソッドですね。
このメソッドはIFilter<T>インターフェイスを実装しているクラスなら何でも受け取ります。
publicになっているので、外部のクラスから使用されることを意図しています。
virtual修飾子はついていないので、サブクラスでoverrideされることは意図していないようですね。
メソッドの最後でreturn this;として自分自身を返しているのは、いわゆるメソッドチェーンの形で利用できるようにするためでしょう。
メソッドチェーンは好きなので、僕的にもこれは大歓迎です。

さて最後はProcessです。
意図としては「パイプラインでの処理の開始」でしょうか。
こちらは抽象メソッドになっているため、具象クラスが実装する必要があります。
また、publicメソッドなので基本的には外部から利用されることが意図されているでしょう。
Processメソッドについては具体的な実装を見たほうがよいかなと思うので、ここではこんなもんで。

Piplineの具象型を作成する

参考サイトを見ながらPiplineの具象型を作成してみます。
とりあえずこんな感じでしょうか。

さてProcessメソッドが実装されています。
これはT型を受け取りT型を返す抽象メソッドでした。
で、T型はここでは何かというと、IEnumerable<Diary>型です。
あらかじめ登録されてあったフィルタを次々と実行(適用)しているのがコードからわかります。
ここでは単にフィルタを適用しているだけですが、オーバーライドするときに何か別の処理を付け加えるのもアリでしょう。
例えばですが「フィルタされた回数を記録する」なんかができますね。

最後にinputを返しているのは、要はフィルタした結果の返却ですね。

フィルタを作成していく

ここまででほぼほぼ完成で、あとはフィルタの内容を作成していくだけです。
今回は

・暗号化された日記だけを表示するフィルタ
・晴れの日記だけを表示するフィルタ
・今年の日記だけを表示するフィルタ
・指定文字数以上の日記だけを表示するフィルタ

を作ってみようと思います。

まずは暗号化された日記だけを表示するフィルタです。

IFilter<T>インターフェイスを実装していますね。
Executeメソッドの中身は単にLINQを使って内容を絞り込んでいるだけです。

残りも一気に行きます。

どれもLINQで絞り込んでいるだけですね。
WordsCountFilterはいい名前が思い浮かばなかったのでクラス名が適当ですが、コンストラクタで与えられた文字数以上の日記だけを表示するフィルタです。
他のクラスは全部EncryptedFilterと似たり寄ったりですね。

使ってみる

さてここまででPipeline and Filters Patternの全てが整いました。
実際に使っていきたいと思います。

いかがでしょーか。
これだけならぶっちゃけ普通にLINQでフィルタする方が早いんじゃないのと思わないでもないのですが、きっと色々と応用が効くのでしょうね。
最近色々なところでPipelineという用語を聞くので、もう少し応用例も勉強してみたいですね。
あ、あとコードのこれが重複しまくってるのでなんとかしたい感あります。

終わりに

久しぶりにデザインパターンを勉強した気がします。
このパターンをゲーム制作においてどのように応用するか? というのは今後の課題だと思うので、色々なサイトの実装例を見ていきたいなーと思います。
ほなそんな感じでまた。

あ、お仕事も募集してます。

コード全体もペタリ。

フォローする