T_Y_CODE

プログラミング学習記録

学習記録 9日目

9日目の学習記録をまとめていきます。

学習計画

学習内容

オブジェクト指向設計実践ガイド 3章

  • 振る舞いが他のオブジェクトに影響を与える時、オブジェクト自身は他のオブジェクトを知っていないといけない。しかし、知っているというのは同時に依存も作りだしてしまいます。慎重な管理が必要になります。
  • 以下のコードは前回作成したコードを少し変更したもの。このGearのWheelに対する依存は、少なくとも4つあります。
    • 他のクラスの名前を使用している。GearはWheelという名前のクラスが存在することを予想している。
    • self以外のどこかに贈ろうとするメッセージの名前がある。GearはWheelのインスタンスがdiameterに応答することを予想している。
    • GearはWheel.newにrimとtireが必要なことを知っている。
    • GearはWheel.newの引数の順番を知ってる。
class Gear
  attr_reader :chainring, :cog, :rim, :tire
  def initialize(chainring, cog, rim, tire)
    @chainring = chainring
    @cog = cog
    @rim = rim
    @tire = tire
  end

  def gear_inches
    ratio *  Wheel.new(rim, tire).diameter
  end

  def ratio
    chainring / cog.to_f
  end
#...
end

class Wheel
  attr_reader :rim, :tire
  def initialize(rim, tire)
    @rim = rim
    @tire = tire
  end

  def diameter
    rim +  ( tire * 2 )
  end
#...
end
  • 上記変更はWheelクラスが変更された際にGearクラスの変更が強制される可能性を高めています。一定の依存関係がこの2つのクラス間に築かれるのは避けられない。
  • オブジェクト指向設計ではそれぞれのクラスが持つ依存を最低限にする。クラスが知るべきことは、自身の責任を果たすために必要十分なことのみでよい。おまけは必要ない。
  • 「『何かを知るオブジェクト』を知るオブジェクト」を知るオブジェクト...のようにいくつものメッセージをチェーンのようにつないで、遠くのオブジェクトに存在する振る舞いを実行しようとすることは破壊的な依存関係を持ってしまう。もとのオブジェクト, 途中のオブジェクトやメッセージ, 目的のオブジェクト全てに依存関係を持ってしまう。これはデメテルの法則に違反している。
  • 上記コードで分かりやすい依存はgear_inchesメソッド内のWheelクラスのインスタンスを作成しているところ。gear_inchesはWheelクラス以外のものを受付ようとしない。今後他のオブジェクトが出てきたとしてもgear_inchesを使用することは出来ない。問題を解決するためには引数にdiameterメソッドを持つクラスのインスタンスを持ってくることだ。
class Gear
  attr_reader :chainring, :cog, :wheel
  def initialize(chainring, cog, wheel)
    @chainring = chainring
    @cog = cog
    @wheel = wheel
  end

  def gear_inches
    ratio *  wheel.diameter
  end
#...
end
  • 上記コードによりGearは@wheel変数を保持し、wheelメソッドでアクセスするようになります。GearはこのオブジェクトがWhheelクラスのインスタンスであるかは気にしていません。Gearが知っているのは単に@wheelはdiameterに応答するオブジェクトを保持しているということだけです。
  • Wheelクラスのインスタンス作成をGearクラスの外で行うことでオブジェクト間の結合が切り離される。Gearクラスはdiameterを実装するオブジェクトであればどれとでも共同作業が出来るようになります。
  • 上記テクニックは「依存オブジェクトの注入」という。Gearは他オブジェクトへの知識を減らすことで、より賢くなりました。
  • gear_inchesメソッドは現状簡潔な内容になっているが将来的に複雑な計算が必要になった時、gear_inches内のwheel.diameterはgaer_inchesへの依存関係を強くしてしまう。
def gear_inches
  #... 恐ろしい計算が何行かある
  foo = some_intermediate_result * wheel.diameter
  #... 恐ろしい計算が何行かある
end
  • gear_inchesメソッド内が複雑化し、その中に外部クラスのメソッドに頼らなければならないコードがある場合、そのコードを内部の専用メソッドにカプセル化してあげる必要がある。
def gear_inches
  #... 恐ろしい計算が何行かある
  foo = some_intermediate_result * diameter
  #... 恐ろしい計算が何行かある
end

def diameter
  wheel.diameter
end
  • Gearクラス内に散りばめられたwheel.diameterはこのカプセル化されたメソッド内に収束されます。(DRY) gear_inchesが他のオブジェクトへの参照を行っていた場合、wheelとの依存関係は他のオブジェクトにまで広がります。このラッパーメソッドはgear_inchesから依存関係を取り除いてくれたのです。
  • initializeメソッドは引数の順番が固定されており、newメッセージの送り手は、引数の順番に依存しています。
class Gear
  attr_reader :chainring, :cog, :wheel
  def initialize(chainring, cog, wheel)
    @chainring = chainring
    @cog = cog
    @wheel = wheel
  end
#...
end

Gear.new( 52, 11, Wheel(26, 1.5) ).gear_inches
  • 引数にハッシュまたはキーワード引数を使用することで上記依存は回避出来る。(Ruby2.0.0以降なら簡潔に書けるキーワード引数の方が良い気がする)
class Gear
  attr_reader :chainring, :cog, :wheel
  def initialize(args)
    @chainring = args[:chainring]
    @cog = args[:cog]
    @wheel = args[:wheel]
  end
#...
end

Gear.new( { chainring: 52, cog: 11, wheel: Wheel(26, 1.5) } ).gear_inches
class Gear
  attr_reader :chainring, :cog, :wheel
  def initialize(chainring: ,cog: , wheel: )
    @chainring = chainring
    @cog = cog
    @wheel = wheel
  end
#...
end

Gear.new( chainring: 52, cog: 11, wheel: Wheel(26, 1.5) ).gear_inches
  • もしGearクラスが外部フレームワークのクラスで引数が固定されている場合は以下の様にラッパークラスを作りキーワード引数化することが可能です。

|

module SomeFramework
  class Gear
    attr_reader :chainring, :cog, :wheel
    def initialize(chainring, cog, wheel)
      @chainring = chainring
      @cog = cog
      @wheel = wheel
    end
  #...
  end
end

module GearWrapper
  def self.gear(chainring: ,cog: , wheel:)
    SomeFramework::Gear.new(chainring, cog, wheel)
  end
end

GearWrapper.gear( chainring: 52, cog: 11, wheel: Wheel(26, 1.5) ).gear_inches
  • GearWrapperモジュールは他のクラスのインスタンスを作成するためだけに書かれている。オブジェクト指向設計ではこのようなオブジェクトを「ファクトリー」と呼ぶ。
  • 今までの例では依存の方向がGearクラス→Wheelクラスとなっていた。この方向は逆に書くことも可能である。現在のスケールのアプリケーションでは依存方向を逆にしたところで大した変化はない。だが今後GearとWheelどちらが変化が大きいかによって依存方向を定めた方が良い。そのような依存の方向を定める基準を以下に示す。
    • 今後変化が多そうなクラスへの依存は注意が必要である。アプリケーション無いのクラスは、変化の起きやすさによって順位付け出来、それが依存方向を決める際の1つの鍵となる。
    • 抽象性とは「いかなる特定のインスタンスからも離れている」ということ。具象性はその逆。具象性の高いクラスは変化が起きやすい。よって抽象性の高いクラスに依存方向を向けた方が良い。
    • 多くの依存関係を持つクラスは影響が大きい。多くのところから依存されたクラスを変更することによる影響は大きい。
  • 自分より変更されないものに依存すること。

リーダブルコード 14章

  • 複雑な記述をしているテストはヘルパーメソッドを使って見た目をきれいにする。(DRYも意識する)
  • テストのエラーコードは分かりやすい文章にする。
  • テストする値は分かりやすいものにする。不必要に複雑な数値等でテストしないこと。一見して結果が分かる数値が良い。
  • 1つの機能に複雑なテストをするより複数の単純なテストをする方が良い。
  • テストはやりすぎないように。テストのために実行コードの読みやすさを犠牲にしては本末転倒。

スッキリわかるSQL入門 3章

  • WHERE句は条件式を用いて行の絞り込みを行っている。
  • WHERE句でRubyでいう.nil?を使用するには式の後にIS NULLを使用する。式=NULLとするとエラーが発生する。これは=演算子が引数に数値を指定していて、NULLが引数になることを想定していないため。
  • 部分一致を行うには以下のように記述する。
/* <キーワード>が含まれる行を検索 */
/* %は0文字以上の文字列 */
/* _は1文字以上の文字列 */
SELECT * FROM <テーブル名> WHERE <列名> LIKE '%<キーワード>%'
SELECT * FROM <テーブル名> WHERE <列名> LIKE '_<キーワード>_'
  • 数値の範囲指定は以下のように記述する。
SELECT * FROM <テーブル名>
WHERE <列名> BETWEEN <値1> AND <値2>
  • 列の項目値の複数一致または不一致を取得するには以下のように記述する。
/* 一致 */
SELECT * FROM <テーブル名>
WHERE <列名> IN ( <キーワード1>, <キーワード2> )
/* 不一致 */
SELECT * FROM <テーブル名>
WHERE <列名> NOT IN ( <キーワード1>, <キーワード2> )
  • 複数の数値と比較演算する場合は以下の用に記述する。基本比較演算子とは<, >, <>, =などである。
/* ANY演算子 */
/* 全ての値に対し||(OR)的な働きをする */
SELECT * FROM <テーブル名>
WHERE <列名>  <基本比較演算子> ANY ( <値1>, <値2> )
/* ALL演算子 */
/* 全ての値に対し&&(AND)的な働きをする */
SELECT * FROM <テーブル名>
WHERE <列名>  <基本比較演算子> ALL ( <値1>, <値2> )

スッキリわかるSQL入門 4章

  • SELECT文にはSELECT文で抽出した表を加工出来る修飾子がある。
  • 重複の削除は以下の修飾子を使用する。
/* 最初に取得した行以外を取得しないようにする */
SELECT DISTINCT <列名> FROM <テーブル名>
  • 取得した表の昇降を並べ替えて表示するには以下の修飾子を使用する。修飾子の対象列は複数設定可能。複数設定した場合最初に書いた列から評価していきその中で同値があれば次の修飾子で指定した列順に並び替える
/* ASC: 昇順, DESC: 降順 */
SELECT <列名> FROM <テーブル名>
ORDER BY <基準にする列名> ASC
/* ORDER BY修飾子は連続使用可能 */
SELECT <列名> FROM <テーブル名>
ORDER BY <基準にする列名> DESC, <基準にする列名> DESC 
  • 表示する表数を制限したい場合は以下の修飾子を使用する。
SELECT <列名> FROM <テーブル名>
ORDER BY <基準にする列名> DESC -- 降順
OFFSET <先頭から除外する数> ROWS
FETCH NEXT <表示数> ROWS ONLY
  • 別々のテーブルを一括で処理したい場合は以下の集合演算子を利用する。但し列名のデータ型は同じでないといけない。
/* 和集合 */
SELECT <列名> FROM <テーブル名A>
UNION
SELECT <列名> FROM <テーブル名B>
/* 差集合 */
/* 基準は最初に記載したテーブルA */
SELECT <列名> FROM <テーブル名A>
EXCEPT
SELECT <列名> FROM <テーブル名B>
/* 積集合 */
SELECT <列名> FROM <テーブル名A>
INTERSECT
SELECT <列名> FROM <テーブル名B>

本日の総括

  • Linuxの標準教科書は一旦学習終了します。6章以降の内容は現時点で必要性を感じていないためです。必要性を感じたら都度読んでいきます。
  • SQLについてある程度読み書き出来るようになってきた。
  • オブジェクト指向設計について学ぶのはとても勉強になる。今後ポートフォリオ等作成する際の基盤となる設計思想なのでしっかり学んでいきたい。