構造体のようなオブジェクトを扱う
オブジェクト指向言語使っていても、ただの値の塊としか利用されないケースに遭遇します。
class Foo def initialize(attr_a, attr_b, attr_c) @attr_a = attr_a @attr_b = attr_b @attr_c = attr_c end def do_something(info) info.attr_1 = info.attr_1 * @attr_a info.attr_2 = info.attr_2 * @attr_b info.attr_3 = info.attr_3 * @attr_c end end class Info attr_accessor :attr_1, :attr_2, :attr_3 def initialize(attr_1, attr_2, attr_3) @attr_1 = attr_1 @attr_2 = attr_2 @attr_3 = attr_3 end end foo = Foo.new(2, 3, 5) info = Info.new(7, 11, 13) foo.do_something(info) pp info #=> #<Info:0x0000000120c09c90 @attr_1=14, @attr_2=33, @attr_3=65>
ベタ書きがよくないからと手続きを分離しても、関数に分離するところまで止まってしまうことがあります。
class Foo # ... def do_something(info) do_something1(info) do_something2(info) do_something3(info) end private def do_something1(info) info.attr_1 = info.attr_1 * @attr_a end def do_something2(info) info.attr_2 = info.attr_2 * @attr_b end def do_something3(info) info.attr_3 = info.attr_3 * @attr_c end end
構造体のようなオブジェクトを能動的なオブジェクトで包む
構造体のようなオブジェクトを定義しているのが自分たちであれば #do_something1 を Info へ移動して、info.do_something1(@attr_a) としたいところですが、Info を直接変更することができないならば、一層包んでやる必要があります。
たとえば Info を包む Attributes を用意して、Attributes に #do_something1 を移動してみます。
class Foo def initialize(attr_a, attr_b, attr_c) @attr_a = attr_a @attr_b = attr_b @attr_c = attr_c end def do_something(attributes) attributes.do_something1(@attr_a) attributes.do_something2(@attr_b) attributes.do_something3(@attr_c) end end class Info attr_accessor :attr_1, :attr_2, :attr_3 def initialize(attr_1, attr_2, attr_3) @attr_1 = attr_1 @attr_2 = attr_2 @attr_3 = attr_3 end end class Attributes def initialize(info) @info = info end def do_something1(attr) @info.attr_1 = @info.attr_1 * attr end def do_something2(attr) @info.attr_2 = @info.attr_2 * attr end def do_something3(attr) @info.attr_3 = @info.attr_3 * attr end end foo = Foo.new(2, 3, 5) info = Info.new(7, 11, 13) attributes = Attributes.new(info) foo.do_something(attributes)
欲しい手続きを持ったオブジェクトで包む方法はよくやるのですが、今回のように操作する間だけ包み、操作後は元のオブジェクトを利用するという使い方をするばあいは、いつオブジェクトが変更されたのか隠されてしまうという難点があります。
手続きをオブジェクトにして分離する
こういったときには手続きを分離してしまうのがよいのかも知れません。 手続きを担うオブジェクトを導入します。
class Foo attr_reader :attr_a, :attr_b, :attr_c def initialize(attr_a, attr_b, attr_c) @attr_a = attr_a @attr_b = attr_b @attr_c = attr_c end def do_something(processor, data) processor.do_something1(self, data) processor.do_something2(self, data) processor.do_something3(self, data) end end class Info attr_accessor :attr_1, :attr_2, :attr_3 def initialize(attr_1, attr_2, attr_3) @attr_1 = attr_1 @attr_2 = attr_2 @attr_3 = attr_3 end end class Processor def do_something1(foo, info) info.attr_1 = info.attr_1 * foo.attr_a end def do_something2(foo, info) info.attr_2 = info.attr_2 * foo.attr_b end def do_something3(foo, info) info.attr_3 = info.attr_3 * foo.attr_c end end foo = Foo.new(2, 3, 5) info = Info.new(7, 11, 13) processor = Processor.new foo.do_something(processor, info)
Foo#do_something は必要な操作の並びを指揮し、個々の操作の詳細は専用のオブジェクトに任せる。
手続きの集まりのオブジェクト
ここで、それぞれの手続きは固定の操作だからとハードコードしてしまうと、二つ目に書いた「関数に分離」しただけの硬直したコードに戻ってしまいます。
結局のところこのオブジェクトは二つのオブジェクトの関係を表現しているのではないかと思います。 手続きしか持たないために対応する実態がないようにも感じられますが、同じオブジェクトの間にも複数の関係があると考えれば、それらが異なるオブジェクトで表現されることも自然に感じられるようになると思います。
foo = Foo.new(2, 3, 5) info = Info.new(7, 11, 13) processor = AnotherProcessor.new foo.do_something(processor, info)
状態を持たない ≠ スタティックな存在
「状態を持たない」と説明されたときに、状態を持たない = インスタンス変数を持たない = インスタンスは不要 と捉えて、すべてのメソッドをクラスメソッド、つまり関数として定義するパタンを何回か目にしました。
おそらくこれは誤解で、上で示したとおり状態がなくてもオブジェクトとして捉えることは可能です。 そもそも、状態はなくても個々のオブジェクトに固有のパラメータを持たせることで、操作の際にそれぞれ個性の違うオブジェクトとして振る舞えるようにすることも考えられるわけで、スタティックな存在とイメージするのはいささか短絡的かなという印象です。
オブジェクト指向は、十分に普及した。 そう思っていましたが、意外とそうでなかったのか。 あるいは、オブジェクト指向を踏襲する必要性がなくなり、このような技法も不要になりつつあるのか。 もう少し観察してみたいところです。
いつか読むはずっと読まない:石垣の中の県庁
福井県の県庁は、福井城址に建てられています。
つまり、石垣の中に県庁があります。
「石垣の中の県庁」と「黒曜石の中の不死鳥」とは似てそうと思ったけれど、そんなに似てなかった。
