.make_hash_list_in_arrayと.stdio_coverageをリファクタリングして、ちょっとだけ格好良くしてみました。
別の投稿の記事を書いていたら、ふと表題の二つのメソッドをもう少し
格好良く出来ないかと願望が芽生えてきたので、やってみました。
以前の記事は、こちら。
変更方針:
呼び出しが、どちらも、
make_hash_list_in_array(hash_list)
stdio_coverage(hash_list)
と関数的でオブジェクト指向っぽくなかったので、
has_list.make_hash_list_in_array
hash_list.stdio_coverage
と使えるようにする。
手にすることが出来る知識:
例によって泥臭い手法
refine/usingと云う保険の使い方
Step1.
make_hash_list_in_array(hash_list)
をどうしたら、
has_list.make_hash_list_in_array
と使えるようになるのか?
簡単である。レシーバーにメッセージが通るようにすればいい。
hash_listは、
[{fruit: 'apple', price: 300}, {fruit: 'banana', price: 100}, {fruit: 'cherry', price: 500}]
といった、複数のハッシュの要素を持つ配列なので、要は、Arrayクラスにメッセージが通るようにすれば良いことになる。
早速、改造してみる。
require 'stringio'
module TOOLS
def make_hash_list_in_array # 引数listを削除
ary, *arys = *self # listをselfに変更
key, *value = *ary.zip(*arys)
values = value.map { |v| key.zip(v).to_h }
.to_a
if block_given?
values.map do |h|
yield(h)
end
end
values
end
def stdio_coverage(buf_in = '', buf_out = '') # 引数aryを削除
$stdin = StringIO.new(buf_in)
$stdout = StringIO.new(buf_out)
each do |h| # レシーバーaryを削除
yield h, buf_in, buf_out
buf_in = ''; $stdin.string = buf_in
buf_out = ''; $stdout.string = buf_out
end
$stdin = STDIN
$stdout = STDOUT
end
end
class MyClass
def initialize
@asking = 'あなたは男性ですか?女性ですか?(man/woman)'
@reply_to_man = 'さようなら。'
@reply_to_woman = 'こんにちは。'
end
def greeting
your_reply = ask
my_reply(your_reply)
end
private
def ask
print "#{@asking} ==> "
gets.chomp
end
def my_reply(your_reply)
if your_reply == 'woman'
print "#{@reply_to_woman}\n"
else
print "#{@reply_to_man}\n"
end
end
end
# ココ重要
class Array; include TOOLS; end # ArrayクラスにTOOLSをインクルード
require 'minitest/autorun'
class MyClassTest < MiniTest::Test
# include TOOLS # includeをコメントアウト
def setup
@my_class = MyClass.new
end
def test_greeting
question = [
:question,
'あなたは男性ですか?女性ですか?(man/woman) ==> ',
'あなたは男性ですか?女性ですか?(man/woman) ==> '
]
typing = [:typing, "man\n", "woman\n"]
reply = [:reply, "さようなら。\n", "こんにちは。\n"]
list = [question, typing, reply]
hash_list = list.make_hash_list_in_array do |h| # 引数listがレシーバーになる
h[:exp] = h[:question] + h[:reply]
end
hash_list.stdio_coverage do |h, buf_in, buf_out| # 引数has_listがレシーバーになる
buf_in << h[:typing]
@my_class.greeting
assert_equal h[:exp],
buf_out
end
end
end
こんな感じになりました。
class Array; include TOOLS; end
の位置が、微妙にブサイク。
以前の記事のコメントは、理解の妨げになるので、削除してあります。
Step2.
ただなぁ。。。許されているとはいえ、こんな大もとのクラスに
こんなことしていいのかなぁ。不安。。。
大した悪さをするはずもないのだけど、何か対策を検討する。
メソッドを削除したり、未定義にしたりと検討したものの再定義の仕掛けが大変そうです。かなりの大工事の予感。
何か抜け道は無いかと探したら、ありました!
refine
これは定義したいクラス/モジュールに適用すれば、usingを使用したクラス/モジュールの中でだけ、有効になるのだそうです。
消したり、無効にできなければ、使いたい場所でだけ動けばいい。
これ使いましょ。
使い方も簡単そうです。
モジュールをrefineで包んで、includeしていたコードをusingで置き換えてお終いです。楽チン。
require 'stringio'
module TOOLS
refine Array do # ここにrefineを被せます。
def make_hash_list_in_array # 引数listを削除
ary, *arys = *self # listをselfに変更
key, *value = *ary.zip(*arys)
values = value.map { |v| key.zip(v).to_h }
.to_a
if block_given?
values.map do |h|
yield(h)
end
end
values
end
def stdio_coverage(buf_in = '', buf_out = '') # 引数aryを削除
$stdin = StringIO.new(buf_in)
$stdout = StringIO.new(buf_out)
each do |h| # レシーバーaryを削除
yield h, buf_in, buf_out
buf_in = ''; $stdin.string = buf_in
buf_out = ''; $stdout.string = buf_out
end
$stdin = STDIN
$stdout = STDOUT
end
end
end
class MyClass
def initialize
@asking = 'あなたは男性ですか?女性ですか?(man/woman)'
@reply_to_man = 'さようなら。'
@reply_to_woman = 'こんにちは。'
end
def greeting
your_reply = ask
my_reply(your_reply)
end
private
def ask
print "#{@asking} ==> "
gets.chomp
end
def my_reply(your_reply)
if your_reply == 'woman'
print "#{@reply_to_woman}\n"
else
print "#{@reply_to_man}\n"
end
end
end
require 'minitest/autorun'
class MyClassTest < MiniTest::Test
using TOOLS # includeをusingに置き換え
def setup
@my_class = MyClass.new
end
def test_greeting
question = [
:question,
'あなたは男性ですか?女性ですか?(man/woman) ==> ',
'あなたは男性ですか?女性ですか?(man/woman) ==> '
]
typing = [:typing, "mn\n", "woman\n"]
reply = [:reply, "さようなら。\n", "こんにちは。\n"]
list = [question, typing, reply]
hash_list = list.make_hash_list_in_array do |h| # 引数listがレシーバーになる
h[:exp] = h[:question] + h[:reply]
end
hash_list.stdio_coverage do |h, buf_in, buf_out| # 引数has_listがレシーバーになる
buf_in << h[:typing]
@my_class.greeting
assert_equal h[:exp],
buf_out
end
end
end
メソッドの前にレシーバを付けて、オブジェクト指向っぽい作りになりました。ポンコツのわたしには、これで十分満足!
追記 (2022/6/3):お詫びと仕様上の注意
レシーバからメソッドを呼び出す形式にしたため、過去版では、
exp = 'something message.'
stdio_coverage do |_, _, buf_out|
@something_obj.something_method
assert_equal exp, buf_out
end
と、引数を持たない使い方を許容していました。
今回の版でこのような使い方をするとエラーになります。
exp = 'something message.'
[{}].stdio_coverage do |_, _, buf_out|
@something_obj.something_method
assert_equal exp, buf_out
end
と、[{}]のように空のレシーバーを与えるか、buf_outと比較するexpが必ず存在するはずなので、
[{ exp: 'something message.' }].stdio_coverage do |h, _, buf_out|
@something_obj.something_method
assert_equal h[:exp], buf_out
end
として、使うようお願いします。
スンマセンです。
この記事が気に入ったらサポートをしてみませんか?