オブジェクト指向って結局なんなん?


とりあえずオブジェクト指向ってなんなん?

モノ(オブジェクト)の組み合わせでプログラムを動かしていくという考え方です。
アプリケーションは部品から構成されており、部品同士が相互に作用し合うことで全体が動く(歯車が噛み合う)ようなイメージです。

部品が「オブジェクト」であり、相互作用はオブジェクト間で受け渡される「メッセージ」であると考え、
正しいメッセージを正しいオブジェクトへ届けるには『メッセージの送り手が受け手のことを知っている』必要があります。
そして、相手のことを知っている、つまり2つのオブジェクト間に依存関係が発生するということです。
この「依存関係」を管理することこそオブジェクト指向設計であるということです。

キャッチボールでいうのであれば、大人と子供はお互いの位置を理解しており、
どのくらいの力加減で投げれば相手がキャッチできるかを知っているということになります。(イメージ)

単一責任って?

一つのクラス、メソッドには一つの責任(仕事)とするべきという考え方で、一つの責任を持ったクラス、メソッドであれば、そのクラスに変更が発生した場合、どんな影響があるか把握しやすいというメリットがあります。逆に複数の責任があると変更による影響を把握しづらいです。

クラスが単一責任になっているか確認するには、一文でクラスを説明することができるかを考えることです。
「それと」「または」が含まれているようであれば責務が複数存在する可能性が高いためリファクタリングの余地ありです。

単一責任を図る1つの目安としては、RailsアプリであればRubocopを導入して走らせてみることです。単一責任を考慮していないアプリであればABC Sizeチェックでひっかかることでしょう。
単一責任は1つのメソッド、クラスに仕事をさせすぎない、細かく分けることで責任を分割し、修正しやすいコードを保つという考え方です。

依存関係って?

アプリケーションは事あるごとに変更が施されます。
『メッセージの送り手が受け手のことを知っている』という依存関係において変更というものは厄介なものです。

あなたが複数のサービスにキャリアメールでアカウント登録している場合、
キャリアメールアドレスと各サービスは依存関係であるといえます。

bad.rb
class User
  def twitter
    email = "[email protected]"
  end

  def qiita
    email = "[email protected]"
  end
end

User.new.twitter => "[email protected]"

例えば、あなたがケータイのキャリアを変えるとき、電話番号はそのままで変更できるので便利ですが、
キャリアメールは使えなくなってしまいますよね?この変更によってあなたはどのような修正が必要でしょうか?

→キャリアのメールアドレスを使用しているサービス全てにログインしメールアドレスの変更

考えただけでもゾッとしますよね。そしてもっと最悪なのは、あるサービスでメールアドレスの変更し忘れて2年後、passwordも忘れてしまった。passwordを再設定したい、そんな時に再設定用のメールが届くのは、、、
見ることのできない前のメールアドレス…

こんな最悪の事態を防ぐにはどうすれば良かったのか、
・そもそもキャリアメールは使わず、gmailにしとけば良かった。
・ある転送サービス(妄想)にメールアドレスを設定し、各サービスはそこからメールアドレスを取得するような仕組みにしておけばよかった。

ということですよね?これら両方に言えること
それは、変更があることを考慮した設定にしておけばよかったということです。

good.rb
class User
  attr_reader :email
  def email
    @email = "[email protected]"
  end

  def twitter
    email
  end

  def qiita
    email
  end
end

User.new.twitter => "[email protected]"

つまり、転送サービスのメールアドレス1箇所を変更するだけで各サービスのアカウントのメールアドレスが変更されるように、オブジェクト指向のポイントは変更時のことを考えた設計であれということです。

引数への依存を取り除く

引数が必要なメッセージを送る時、送り手側はそれらの引数についての知識を持たざるを得ません。
この依存は避けられませんが、引数の渡し方によってはもう1つの依存を引き起こしかねません。

class Salary
  attr_reader :work_time :hourly_wage :overtime_fee
  def initialize(work_time, hourly_wage, overtime_fee)
    @work_time = work_time
    @hourly_wage = hourly_wage
    @overtime_fee = overtime_fee
  end

 ...

end

Salary.new(
    160,
    1000,
    OvertimeFee.new(20, 1250) 
)

Salary.new(
    :work_time => 160,
    :hourly_wage => 1000,
    :overtime_fee => OvertimeFee.new(20, 1250) 
)

上記の引数は、initializeの順番に依存しており、引数の順番を見て何の値かが分からなくなってしまいます。
Hashを用いることで引数の順番への依存は取り除かれ、よりオブジェクト指向に近い渡し方になります。

ただ、毎回Hashを用いることが良いとは限りません。少ない引数でHashを用いることで手間が多くなることもあります。そこの加減はプロ塾と単位での統一ルールなどを設ける必要があるのかもしれません。

参考

・『オブジェクト指向設計 実践ガイド』