GoFのデザインパターン(Design Pattern)のひとつ、インタプリタ(Interpreter)をRubyのサンプルコードで紹介します。
インタプリタパターンはひとつひとつの問題はシンプルだが、組み合わさって複雑になるような場合に効果を発揮します。
🏀インタプリタとは?
専用の言語を作り、その言語で得られた手順にもとづいて処理を実行していくデザインパターンです。
インタプリタには次の構成要素があります。
- 抽象表現(AbstractExpression): 共通のインタフェースを定義
- 終端(TerminalExpression): 終端を表現するクラス
- 終端以外(NonterminalExpression): 非終端を表現するクラス
- 状況、文脈(Context): 構文の解析を手助けする
🤔サンプルソース
サンプルとして、ファイル検索用のインタプリタを書いていきます。
まずは、すべてのファイル検索のベースとなる最も単純なクラスを作成します。
# 命令・抽象的な表現(AbstractExpression) # Expression: 共通するコードを持つ classExpression def|(other) Or.new(self, other) end
def&(other) And.new(self, other) end end
|
続いて、すべてのファイル名を返すAllクラスを作成します。
evaluateメソッドの概要は次のとおりです。
- Rubyの標準ライブラリfindを使ってディレクトリ内のファイル名を収集
- Find.findを使うことで、サブフォルダまで含めたすべてのファイルを返す
require"find" # 終端となる表現(構造木の葉) (TerminalExpression) # All: すべてのファイルを返す classAll< Expression defevaluate(dir) results= [] Find.find(dir)do|p| nextunless File.file?(p) results<< p end results end end
|
続いて、与えられたパターンとマッチするすべてのファイル名を返すFileNameクラスを作成します。
# 終端となる表現(構造木の葉) (TerminalExpression) # FileName: 与えられたパターンとマッチするすべてのファイル名を返す classFileName< Expression definitialize(pattern) @pattern = pattern end
defevaluate(dir) results= [] Find.find(dir)do|p| nextunless File.file?(p) # File.basename => ファイルパスからファイル名だけを抽出 name = File.basename(p) # File.fnmatch => ファイル名がパターンにマッチした場合のみtrueを返す results<< pif File.fnmatch(@pattern, name) end results end end
|
さらに、指定したファイルサイズより大きいファイルを返すBiggerクラスを作成します。
# 終端となる表現(構造木の葉) (TerminalExpression) # Bigger: 指定したファイルサイズより大きいファイルを返す classBigger< Expression definitialize(size) @size = size end
defevaluate(dir) results = [] Find.find(dir)do|p| nextunless File.file?(p) results<< pif( File.size(p) > @size) end results end end
|
書込可能なファイルを返すWritableクラスは次のようになります。
# 終端となる表現(構造木の葉) (TerminalExpression) # Writable: 書込可能なファイルを返す classWritable< Expression defevaluate(dir) results = [] Find.find(dir)do|p| nextunless File.file?(p) results<< pif( File.writable?(p) ) end results end end
|
ここで、「書込ができないファイル」を探せるように、Writableクラスを否定できるNotクラスを作ります。Notクラスは、Biggerクラスにも適用できるので、指定したファイルサイズより小さいファイルを探せます。
# 終端以外の表現(構造木の節) NonterminalExpression classNot< Expression definitialize(expression) @expression = expression end
defevaluate(dir) All.new.evaluate(dir) - @expression.evaluate(dir) end end
|
最後に2つのファイル検索式を「Or」「And」で結合するクラスを作ります。
# 終端以外の表現(構造木の節) NonterminalExpression # Or: 2ファイル検索式をORで結合する classOr< Expression definitialize(expression1, expression2) @expression1 = expression1 @expression2 = expression2 end
defevaluate(dir) result1 = @expression1.evaluate(dir) result2 = @expression2.evaluate(dir) (result1 + result2).sort.uniq end end
# 終端以外の表現(構造木の節) NonterminalExpression # And: 2ファイル検索式をANDで結合する classAnd< Expression definitialize(expression1, expression2) @expression1 = expression1 @expression2 = expression2 end
defevaluate(dir) result1 = @expression1.evaluate(dir) result2 = @expression2.evaluate(dir) (result1 & result2) end end
|
上のコードを使ってファイルを検索した結果を下に載せました。
# =========================================== complex_expression1 = And.new(FileName.new('*.mp3'), FileName.new('big*')) puts complex_expression1.evaluate('13_test_data') #=> 13_test_data/big.mp3 #=> 13_test_data/big2.mp3
complex_expression2 = Bigger.new(1024) puts complex_expression2.evaluate('13_test_data') #=> 13_test_data/big.mp3 #=> 13_test_data/big2.mp3 #=> 13_test_data/subdir/other.mp3
complex_expression3 = FileName.new('*.mp3') & FileName.new('big*') puts complex_expression3.evaluate('13_test_data') #=> 13_test_data/big.mp3 #=> 13_test_data/big2.mp3
complex_expression4 = All.new puts complex_expression4.evaluate('13_test_data') #=> 13_test_data/big.mp3 #=> 13_test_data/big2.mp3 #=> 13_test_data/small.mp3 #=> 13_test_data/small1.txt #=> 13_test_data/small2.txt #=> 13_test_data/subdir/other.mp3 #=> 13_test_data/subdir/small.jpg
|
このように単純なインタプリタでも十分にファイル検索を実現できていることがわかります。
🚕サンプルソース
🎂参考リンク
🖥 VULTRおすすめ
「VULTR」はVPSサーバのサービスです。日本にリージョンがあり、最安は512MBで2.5ドル/月($0.004/時間)で借りることができます。4GBメモリでも月20ドルです。 最近はVULTRのヘビーユーザーになので、「ここ」から会員登録してもらえるとサービス開発が捗ります!