Movatterモバイル変換


[0]ホーム

URL:


酒と泪とRubyとRailsと

インタプリタ(Interpreter)


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のヘビーユーザーになので、「ここ」から会員登録してもらえるとサービス開発が捗ります!

📚 おすすめの書籍

Rails/Modelバリデーションコールバック変更前後の値の確認値の保存・更新アソシエーション削除メソッドSQLの実行日付カラムの設定便利なDB関連設定読込み専用(Read Only)Concern(共通処理)クエリTipsRails/Controllerrenderレシピ集HTTPステータスシンボルIPの取得についてStrong ParametersControllerのTipsRails/ViewHAMLチートシート画像アップロードフォームS3への画像アップロードForm Objectフォームヘルパー検索可能なselectタグenum - selectタグシンプルな検索ボックス別ウィンドウ・フォームRailsいろいろi18nについてRails.cacheメール送信ロギングCapistrano3でデプロイセッション管理環境ごとのYAML設定読込xx分前の時間表記Tipsいろいろ便利ライブラリ簡単Webmock:VCRN+1対策:bullet環境変数の管理: dotenvi18nサポート: i18n-tasksLDAP:net-ldapヘッダ:secureheadersrack-mini-profileractiverecord-precountカバレッジ計測パンくずリスト+MetadataRSpecの記法自動修正アプリランキング取得Unicorn導入手順unicorn-worker-killerRSpecController SpecCapybaraとヘッドレスChromeRequest SpecAPI認証 Spec便利Tipsデザインパターン概要アブストラクトファクトリビルダファクトリメソッドアダプタコンポジットデコレータプロキシコマンドインタプリタイテレータオブザーバストラテジテンプレートメソッドRuby基礎文字列/String配列/Arrayハッシュ/Hash構造体/StructSpawn(コマンド実行)BundlerについてTipsRails 5.1へのアップデートRails 5へのアップデートrbenvバージョンアップpumaをsystemdで動かすURI.encodeとCGI.escapeURLのパラメータを変更処理時間を計測画像JPEG/PNG/GIF判定トラブルシュートNo route matches assets

[8]ページ先頭

©2009-2025 Movatter.jp