rubyオブジェクト指向(8)カプセル化

★アクセサメソッド
rubyのコードではattr_accessorというコードがよくでてきます。これは一種のインスタンス変数ですが、attr_accessor、attr_reader、attr_writerの3つがあり、インスタンス変数を読み書きするためのアクセサメソッドと言います。(値をセットするセッターと、値をゲットするゲッターという2つのメソッドをアクセサメソッドと言います)
引数には、インスタンス変数名をシンボルで指定します。複数指定できます。
例えば、attr_reader :nameと書くだけで、インスタンス変数@nameにアクセスできるようになります。

例として以下のコードを考えます。

class Square
  attr_accessor :height,:width
  def initialize(height,width)
    @height = height
    @width = width
  end
end

irb> sq = Square.new(20,30)
irb> sq.height
=> 20
irb> sq.width
=> 30

四角の縦(height)、横(width)を管理するクラスです。
「attr_accessor :height」がインスタンス変数@heightをクラスの外からアクセス可能にするためのコードになります。
「attr_accessor @height」のようには書きません。あくまでシンボルで書きます。これは内部的にセッター、ゲッターのメソッドへのアクセスになるからです。メソッドを使いたいので、シンボルで定義します。

attr_accessorは内部的に次のような処理を行っています。

(1)引数のシンボルすべてに対して以下の処理を繰り返す。
(2)シンボルで指定された名前のメソッドを定義する。そのメソッドはシンボルの名前の先頭に「@」を付加したインスタンス変数の値を取り出す。(ゲッター)
(3)シンボルで指定されたメソッド名の後ろに「=」を付加した名前のメソッドを定義する。そのメソッドは引数を1つ取り、その値をシンボルの名前の先頭に「@」を付加したインスタンス変数に設定する。(セッター)

呼び出すときに書くコードで、「sq.height」のheightはインスタンス変数ではなくインスタンス変数@heightの値を返してくれるメソッドというわけです。

★カプセル化
attr_accessorを使うときは、オブジェクト指向のカプセル化の概念とつながっています。
カプセル化とは、オブジェクト同士の紐付き(関係性)を薄くし、独立性を高め、再利用や交換といった保守性を高める効率の考え方です。
プログラムは常に改善、改造を繰り返し、より良いものになって行きます。その作業を行うプログラマーは、一人とは限りません。多くの人が関係します。また、時間経過が長く、作ったものを忘れてしまうときもあります。そういう人のたずさわるプログラムにミスやバグが入り込まないようにすることが目的です。

例えば最初にアクセサメソッドを持った四角クラスを考えます。このクラスでは、四角の面積を計算するメソッドと、正方形か否かを返すメソッドの2つを持っているとします。

class Square
  attr_accessor :height,:width
  def initialize(height,width)
    @height = height
    @width = width
  end
  # 面積計算
  def calc_area
    @height * @width
  end
  # 正方形の時trueになる
  def square?
    if @height == @width
      true
    else
      false
    end
  end
end

イニシャライザで縦と横の値を渡して、インスタンス変数として持ち、面積計算、正方形か否かのメソッドでは、引数を受け取らずに内部のインスタンス変数を使って結果を返します。
呼び出すときは次のようになります。

irb> sq = Square.new(20,30)
irb> sq.square?
=> false
irb> sq.height = 30
=> 30
irb> sq.calc_area
=> 900
irb> sq.square?
=> true

このクラスの呼び出す側から考えた依存度は最小限です。インスタンス化のときに縦、横の数値を渡し、必要に応じて縦、横の数値をセッターで変更し、各メソッドを呼び出せば計算値が受け取れますので。

もしこれをアクセサメソッドを使わずに書くとどうなるでしょうか。

class Square
  # 面積計算
  def calc_area(height,width)
    height * width
  end
  # 正方形の時trueになる
  def square?(height,width)
    if height == width
      true
    else
      false
    end
  end
end

イニシャライザは不要になり、各メソッドの呼び出しで引数として縦、横の数値をもらうことになります。呼び出すときは次のようになります。

irb> sq = Square.new
irb> sq.square?(20,30)
=> false
irb> sq.calc_area(30,30)
=> 900
irb> sq.square?(30,30)
=> true

インスタンス化のときには何も値を渡しませんが、各メソッドの呼び出しのときに引数で縦、横の数値を渡しています。
このクラスのインスタンス化は1箇所で行うとして、各メソッドが色々なところから呼ばれるコードになっているとすると引数の数値2つは冗長です。いつも縦、横の数値が必要になります。

もしこの四角クラスを立体も扱うように改造しようとする必要が出てきたとき、奥行(depth)という変数も引数に加える必要があり、メソッドの呼び出し箇所を一斉に変更する必要が出てきます。
しかし、アクセサメソッドを使ったクラスの場合では、attr_accessorに奥行(:depth)のシンボルを追加し、メソッド内でその値を使った計算ロジックに組み直せばいいだけになります。(もちろんメソッド名に変更はないということが前提ですが)

このようにいろんなオブジェクトから呼ばれることを想定し、呼ぶ側、呼ばれる側それぞれに変更があったとしても構造的な影響が最小限になるよう考えておくのがオブジェクト指向プログラミングの醍醐味です。

この記事が気に入ったらサポートをしてみませんか?