エンジニアのソフトウェア的愛情

または私は如何にして心配するのを止めてプログラムを・愛する・ようになったか

オブジェクトの手続きと手続きのオブジェクト

構造体のようなオブジェクトを扱う

オブジェクト指向言語使っていても、ただの値の塊としか利用されないケースに遭遇します。

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_something1Info へ移動して、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)

状態を持たない ≠ スタティックな存在

「状態を持たない」と説明されたときに、状態を持たない = インスタンス変数を持たない = インスタンスは不要 と捉えて、すべてのメソッドをクラスメソッド、つまり関数として定義するパタンを何回か目にしました。

おそらくこれは誤解で、上で示したとおり状態がなくてもオブジェクトとして捉えることは可能です。 そもそも、状態はなくても個々のオブジェクトに固有のパラメータを持たせることで、操作の際にそれぞれ個性の違うオブジェクトとして振る舞えるようにすることも考えられるわけで、スタティックな存在とイメージするのはいささか短絡的かなという印象です。

オブジェクト指向は、十分に普及した。 そう思っていましたが、意外とそうでなかったのか。 あるいは、オブジェクト指向を踏襲する必要性がなくなり、このような技法も不要になりつつあるのか。 もう少し観察してみたいところです。

いつか読むはずっと読まない:石垣の中の県庁

福井県の県庁は、福井城址に建てられています。

福井城とは? (PDF)

つまり、石垣の中に県庁があります。

「石垣の中の県庁」と「黒曜石の中の不死鳥」とは似てそうと思ったけれど、そんなに似てなかった。