Forwardable モジュールでのメソッド移譲

メソッドの移譲について学んだのでメモです。

参考

実行環境

  • Ruby 3.0.0

実装

Forwardable を使っていないパターン

test3.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
require 'ostruct'

class Stock
attr_reader :items

def initialize(items)
@items = items || []
end

def last_ones
items.select{|i| i.count == 1 }
end
end

item1 = OpenStruct.new({ name: "saba", count: 3 })
item2 = OpenStruct.new({ name: "maguro", count: 1 })

def format_item(i) = "#{i.name}:#{i.count}個"

stock = Stock.new([item1, item2])

stock.items.each{|i| puts format_item(i) }
# => saba:3個
# maguro:1個

stock.last_ones.each{|i| puts format_item(i) }
# => maguro:1個

Forwardable を使ってみる

test2.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
require 'forwardable'
require 'ostruct'

class Stock
extend Forwardable
def_delegators :@items, :each

def initialize(items)
@items = items || []
end

def last_ones
@items.select{|i| i.count == 1 }
end
end

item1 = OpenStruct.new({ name: "saba", count: 3 })
item2 = OpenStruct.new({ name: "maguro", count: 1 })

def format_item(i) = "#{i.name}:#{i.count}個"

stock = Stock.new([item1, item2])

stock.each{|i| puts format_item(i) }
# => saba:3個
# maguro:1個

stock.last_ones.each{|i| puts format_item(i) }
# => maguro:1個

Stack に対して、each を呼び出すと応答するオブジェクトが @items に代わっている。

変更前は、itemsに問い合わせる必要がある。(ということを知っている必要がある。)

1
stock.items.each{|i| puts format_item(i) }

変更後は、インスタンスに直接問い合わせるだけで OK。

1
stock.each{|i| puts format_item(i) }

Enumerable も使ってみる

Enumerable は、繰り返し処理を行うための Mix-in だそう。
これを使うと、last_ones が、もっとシンプルになる。

test3.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
require 'forwardable'
require 'ostruct'

class Stock
extend Forwardable
def_delegators :@items, :each

include Enumerable
def initialize(items)
@items = items || []
end

def last_ones
select{|i| i.count == 1 }
end
end

item1 = OpenStruct.new({ name: "saba", count: 3 })
item2 = OpenStruct.new({ name: "maguro", count: 1 })

def format_item(i) = "#{i.name}:#{i.count}個"

stock = Stock.new([item1, item2])

stock.each{|i| puts format_item(i) }
# => saba:3個
# maguro:1個

stock.last_ones.each{|i| puts format_item(i) }
# => maguro:1個

Stockクラスに、Enumerableを読みこむことでselectメソッドを備えることができるようになりました。
Enumerableは、クラスが each メソッドを備えることが必要であるので、ほとんどForwardableeachを移譲するのがセットの運用と考えられそう。
(eachメソッドを別途作っている記事を見かけたので、またあとで試そうかと。)

ファクトリパターンを作ってみる。

これらを使ってファクトリパターンの実装を試してみます。

test4.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
require 'forwardable'
require 'ostruct'

class Stock
extend Forwardable
def_delegators :@items, :each, :push

include Enumerable
def initialize(items)
@items = items || []
end

def last_ones
select{|i| i.count == 1 }
end
end

module StockFactory
# パラメータをOpenStructで入れなおしてStockクラスのインスタンスを返す
def self.build(config)
items = config.collect {|c| OpenStruct.new({ name: c[0], count: c[1] })}
Stock.new(items)
end
end

def format_item(i) = "#{i.name}:#{i.count}個"


config = [["saba",3 ], ["maguro", 1]]
add_item = OpenStruct.new({ name: "tara", count: 4 })

stock = StockFactory.build(config)

stock.each{|i| puts format_item(i) }
# => saba:3個
# maguro:1個

stock.last_ones.each{|i| puts format_item(i) }
# => maguro:1個

stock.push(add_item)
stock.each{|i| puts format_item(i) }
# => saba:3個
# maguro:1個
# tara:4個

ファクトリーパターンの適用とともに、pushメソッドも移譲をしてみました。


今回は、Forwardable モジュールでのメソッド移譲を手始めに、ファクトリーパターンの実装を試しました。
クラスに、直接eachといったメソッドを移譲させられると、中のデータをオブジェクトの外に晒さなくて良くなるのは利点と感じます。
また関連のメソッドチェーンにもつなぎやすいと感じます。

今回、Forwardable や Enumerable を試しましたが、標準で用意されているライブラリについての認識持っておくのって絶対大事ですね。

ではでは。