22Inc. サービス開発日誌

スタンプスのサービス開発チームが日々の業務で得たノウハウ、経験の共有ブログです。

夏休みの読書感想文 : オブジェクト指向設計 実践ガイド

はじめまして。22Inc.にエンジニアとして中途入社しました藤原です。 実は、昔に営業代理店で「スタンプス」を販売していた経験があります。 そんな私が偶然、縁があり22incにエンジニアとして拾っていただきました。

さて、夏休みの読書感想文を書いていきたいと思います。

読んだ本:

本を読む前まで

ダックタイピングってなに?

もしもそれがアヒルのように歩き、アヒルのように鳴くのなら、それはアヒルである。

へ〜よくわからんけど、まだ自分には早い技術やな。

オブジェクト指向ってなに?

  • クラスがあるやつやろ?
  • 継承ってのが便利みたい。
  • インスタンスがつくれる(クラスは設計書みたいな感じ)

恥ずかしながらそんなレベルでした。

では、オブジェクト指向を少し身に着けた軌跡をまとめていきます。

オブジェクト指向のオブジェクトの考え方

感動した言葉があったので引用

仮にオブジェクトが人間だとして、自分の関係を説明できるとしましょう。すると、図4.5では、TripはMechanicに「私は自分が何を望んでいるかを知っているし、あなたがそれをどうやるかも知っているのよ」と伝えているはずせす。図4.6では、「私は自分がなにを望んでいるかを知っていて、あなたが何をするのかも知っているよ」、図4.7では、「私は自分が何を望んでいるかを知っているし、あなたがあなたの担当部分をやってくれると信じているよ」でしょう

オブジェクト指向とそうでないコードの対比

では、具体的にオブジェクト指向なコードとそうでないコードのを比較してみます。 本書にでてくる4章のシーケンス図の例を参考にしながらRailsで自分なりにコードにしてみました。

自転車旅行会社で、旅行の前に整備士が自転車の準備をするプログラム

オブジェクト指向でないコード

Tripクラスのprepare_bicyclesメソッドは、Mechanicクラスをメソッド内で使っており、かつMechanicクラスのメソッドの引数にどのようなデータを渡すかまで知っておりそれも、使っている。 つまり、「私は自分が何を望んでいるかを知っているし、あなたがそれをどうやるかも知っているのよ」という状況

class Trip < ApplicationRecord
  has_many :trips

  def prepare_bicycles
    self.bicycles.each do |bicycle|
      Mechanic.clean_bicycle(bicycle)
      Mechanic.pump_tires(bicycle)
      Mechanic.lube_chain(bicycle)
      Mechanic.check_brakes(bicycle)
    end
  end

end

class Bicycle < ApplicationRecord
  belongs_to :trip, optional: true
end

class Mechanic < ApplicationRecord

  def self.clean_bicycle(bicycle)
    bicycle.update(is_clean: true)
  end

  def self.pump_tires(bicycle)
    bicycle.update(is_pump_tires: true)
  end

  def self.lube_chain(bicycle)
    bicycle.update(is_lube_chain: true)
  end

  def self.check_brakes(bicycle)
    bicycle.update(is_check_brakes: true)
  end

end

trip = Trip.find(1)
trip.prepare_bicycles

オブジェクト指向なコード

Tripクラスの内には、MechanicクラスがなくMechanicに関することを何も知らない。Mechanicクラスのprepare_trip(trip)メソッドで引数としてオブジェクトが注入され、アソシエーションのbicyclesメソッドが呼び出される。Mechanicクラスに引数として渡り、どのように処理されるかはMechanicクラスに任せている。 つまり、「私は自分が何を望んでいるかを知っているし、あなたがあなたの担当部分をやってくれると信じているよ」という状況

class Trip < ApplicationRecord
  has_many :bicycles
end

class Bicycle < ApplicationRecord
  belongs_to :trip, optional: true
end

class Mechanic < ApplicationRecord

  def self.prepare_trip(object)
    object.bicycles.each do |bicycle|
      self.prepare_bicycle(bicycle)
    end
  end

private

  def self.prepare_bicycle(bicycle)
    self.clean_bicycle(bicycle)
    self.pump_tires(bicycle)
    self.lube_chain(bicycle)
    self.check_brakes(bicycle)
  end

  def self.clean_bicycle(bicycle)
    bicycle.update(is_clean: true)
  end

  def self.pump_tires(bicycle)
    bicycle.update(is_pump_tires: true)
  end

  def self.lube_chain(bicycle)
    bicycle.update(is_lube_chain: true)
  end

  def self.check_brakes(bicycle)
    bicycle.update(is_check_brakes: true)
  end

end

trip = Trip.find(1)
Mechanic.prepare_trip(trip)

オブジェクト指向ならどういうメリットがあるのか

新機能の実装を例に

e.g) 新しく、自転車レースをする事業が始まったため、整備士(Mechanic)は旅行のときだけではなく、自転車レースのときも自転車を準備する必要がでてきた。

オブジェクト指向でないコード

class Trip < ApplicationRecord
  has_many :trips

  def prepare_bicycles
    self.bicycles.each do |bicycle|
      Mechanic.clean_bicycle(bicycle)
      Mechanic.pump_tires(bicycle)
      Mechanic.lube_chain(bicycle)
      Mechanic.check_brakes(bicycle)
    end
  end

end

# >>>>>>>>>>>>> 新しく追加
class Race < ApplicationRecord
  has_many :bicyclesbicycle

  def prepare_bicycles
    self.bicycles.each do |bicycle|
      Mechanic.clean_bicycle(bicycle)
      Mechanic.pump_tires(bicycle)
      Mechanic.lube_chain(bicycle)
      Mechanic.check_brakes(bicycle)
    end
  end

end
# >>>>>>>>>>>>> 


class Bicycle < ApplicationRecord
  belongs_to :trip, optional: true
end

class Mechanic < ApplicationRecord

  def self.clean_bicycle(bicycle)
    bicycle.update(is_clean: true)
  end

  def self.pump_tires(bicycle)
    bicycle.update(is_pump_tires: true)
  end

  def self.lube_chain(bicycle)
    bicycle.update(is_lube_chain: true)
  end

  def self.check_brakes(bicycle)
    bicycle.update(is_check_brakes: true)
  end

end

trip = Trip.find(1)
trip.prepare_bicycles

race = Race.find(1)
race.prepare_bicycles

オブジェクト指向なコード

class Trip < ApplicationRecord
  has_many :bicycles
end

# >>>>>>>>>>>>> 新しく追加
class Race < ApplicationRecord
  has_many :bicycles
end
# >>>>>>>>>>>>> 

class Bicycle < ApplicationRecord
  belongs_to :trip, optional: true
end

class Mechanic < ApplicationRecord

  def self.prepare_bicycles(object)
    object.bicycles.each do |bicycle|
      self.prepare_bicycle(bicycle)
    end
  end

private

  def self.prepare_bicycle(bicycle)
    self.clean_bicycle(bicycle)
    self.pump_tires(bicycle)
    self.lube_chain(bicycle)
    self.check_brakes(bicycle)
  end

  def self.clean_bicycle(bicycle)
    bicycle.update(is_clean: true)
  end

  def self.pump_tires(bicycle)
    bicycle.update(is_pump_tires: true)
  end

  def self.lube_chain(bicycle)
    bicycle.update(is_lube_chain: true)
  end

  def self.check_brakes(bicycle)
    bicycle.update(is_check_brakes: true)
  end

end

trip = Trip.find(1)
Mechanic.prepare_bicycles(trip)

race = Race.find(1)
Mechanic.prepare_bicycles(race)

つまり

オブジェクト指向でないコードの場合はコピペで prepare_bicycles メソッドを必要となるクラスに書く必要がある。 これは、Tripクラスのprepare_bicyclesの内容が変わると、Raceクラスのprepare_bicyclesも修正する必要がある。 オブジェクト指向なコードの場合は、Mechanicクラスのprepare_bicycleメソッドを変更するだけで大丈夫!

感想

はたして今回書いたサンプルコードはオブジェクト指向なのか? 本を読んでる最中はオブジェクト指向を「完全に理解した」が、ブログを書いていると「なにもわからない」という感じになった。 新しくGoFに関する本を買ったのでオブジェクト指向の習得していく! また、オブジェクト指向に関するブログを書いていきます。 TripクラスとRaceクラスでprepare_bicyclesの実装が違うみたいな記事を次書こうかなと思ってます。