君は心理学者なのか?

大学時代に心理学を専攻しなぜかプログラマになった、サイコ(心理学)プログラマかろてんの雑記。

Rubyのmoduleを使って定義したmethodは、インスタンスmethod内で使える。クラスmethod内では使えない。

include先のクラスにて、インスタンスメソッド内で使う

module Hoge  
  def hello
    puts 'hello'
  end
end               

class Fuga
  include Hoge  
  def call_mod_method # インスタンスメソッド
    hello 
  end
end  

fuga = Fuga.new
fuga.call_mod_method

Hoge moduleをincludeしてFugaクラス内でインスタンスメソッドとして使ってます。

結果

hello

ふむ。普通に使える。

include先のクラスにて、クラスメソッド内で使う

module Hoge  
  def hello
    puts 'hello'
  end
end               

class Fuga
  include Hoge  
  def self.call_mod_method # クラスメソッド
    hello 
  end
end  

Fuga.call_mod_method

結果

`call_mod_method': undefined local variable or method `hello' for Fuga:Class (NameError)

おおー。hello methodは定義されていない、と出たぞ。

原因

「クラス内にmoduleをincludeする」

というのは、

「そのクラスにインスタンスmethodを追加する

ということらしい(たのしいRubyより)。

今回、moduleで追加したhelloというインスタンスメソッドを、

クラスメソッドから呼び出してしまったので、そんなもんはねぇよ。

と怒られてしまった。

まとめ

Ruby結構面白いな。

Rubyのselfが微妙に意味がわからなかったので、様々な文脈(トップレベル、class定義内、method定義内、module定義内)で出力してみた

Rubyのselfが微妙にわからん

Rubyのselfが使う割にきちんと理解できていない気がしたので、

いろんな文脈で出力してみた。

トップレベルの文脈でのself

コード

p self

結果

main

トップレベルではmainというオブジェクトがmethodを実行しているらしい。

へぇ。知らなかった。

class定義内、mthod定義内でのself

コード

class Hoge

  # class定義内
  p self

  def self.hello
    # class method定義内
    p self
  end 

  def hello
    # インスタンスmethod定義内
    p self
  end 

end

なにが出力されるのか、結果を見る前に考えてみよう。

結果

class Hoge

  # class定義内
  p self #=> Hoge

  def self.hello
    # class method定義内
    p self #=> Hoge
  end 

  def hello
    # インスタンスmethod定義内
    p self #=> #<Hoge:0x007fc586a99db0>
  end 

end

これの結果を見ると、

  • class定義内、class method定義内のself => classを返す

  • method定義内のself => methodを呼び出した時のインスタンスを返す

ということがわかる。

module定義内でのself

コード

module Hoge  
  def hello
    p self  
  end

  module_function :hello  
end

Hoge.hello

結果

Hoge

module名を返す。なるほど。

moduleをincludeしてトップレベルで呼び出した時のself

コード

module Hoge  
  def hello
    p self  
  end
end

include Hoge
hello

結果

main

トップレベルでのコンテクストになるからmainが返るのか。

へぇ。

moduleをincludeしたクラスから呼び出した時のself

コード

module Hoge  
  def hello
    p self  
  end
end               

class Fuga
  include Hoge  
  def call_mod_method  
    hello  
  end
end

fuga = Fuga.new  
fuga.call_mod_method

結果

#<Fuga:0x007f25e53ade20>

これは実質selfをmethod定義内で呼び出しているので、

インスタンスが返ってくるのは納得。

Rubyのprivate methodはサブクラスからも呼び出せる

PHPRubyのprivate methodを比較してみる

子クラスから親クラスのprivate methodが呼び出せるか、

確かめてみます。

PHPの場合

実行コード

<?php
// 親クラスを定義
class Hoge
{
  private function hello1()
  {
    echo 'hello1 from Hoge';
  }
}

// 子クラスを定義
class HogeExtended extends Hoge
{
  public function hello2()
  {
    parent::hello1();
  }
}

$hogeExtended = new HogeExtended();
// 子クラスから親クラスのprivate methodを呼び出す
$hogeExtended->hello2();

結果

Call to private method Hoge::hello1() from context 'HogeExtended'

呼び出せない。

Rubyの場合

実行コード

class Hoge
  private
  def pri_hello
    puts 'hoge_pri_hello'
  end 
end

class HogeExtend < Hoge
  public
  def ex_pub_hello
    pri_hello # 親クラスのprivate methodを呼び出す
  end 
end

hogeex = HogeExtend.new
hogeex.ex_pub_hello

結果

hoge_pri_hello

呼び出せる。

まとめ

子クラスから親クラスのprivate methodを

  • PHPは呼び出せない(Javaも同じ)

  • Rubyは呼び出せる

正直ビビりました。

劇的ビフォ◯アフターで学ぶデザインパターン(strategy編:after)

前回のソースコードをstrategyパターンを使用して書き換えていく

前回のソースコードから、戦略に関する部分についてクラスで切り出します。

戦略クラス(特定戦略時の振る舞いを持ったクラス)を作成

# ガンガン行こうぜのとき
class StrategyGangan
  def self.attack
    puts 'こうげきまほうをつかった!'
  end 
end

他の2つの戦略についてもクラスとして分離します。

# じゅもんつかうな
class StrategyNotUseJumon
  def self.attack
      puts 'ふつうにこうげきした!'
  end 
end

# いのちをだいじに
class StrategyInochiDaijini
  def self.attack
      puts 'かいふくまほうをつかった!'
  end 
end

Heroクラスが戦略クラスを持つようにする

そして、Heroクラスでは先ほどの戦略クラスをstrategyとして保持するようにします。

class Hero

  attr_accessor :strategy

  def initialize(strategy = StrategyGangan)
    # 引数が渡ってきていない場合、デフォルトでガンガンいこうぜ
    @strategy = strategy
  end 

  # 攻撃をさせる
  def action
    strategy.attack
  end 

  # 作戦切り替え
  def change_strategy(strategy)
    @strategy = strategy
  end

end

前回のソースコードと比較すると、相当スッキリしています。

Heroクラス使用時

hero = Hero.new()
hero.action

hero.change_strategy(StrategyNotUseJumon)
hero.action

hero.change_strategy(StrategyInochiDaijini)
hero.action

結果

こうげきまほうをつかった!
ふつうにこうげきした!
かいふくまほうをつかった!

仕様変更

さて、これでstrategyパターンへの書き換えが終了しました。

ここで例の仕様変更です。

上司「バッチリがんばれを追加な」

strategyパターンを使用した場合、変更箇所はどう変わるでしょうか。

比較

  • strategyパターンを使用しない場合

3行

  • strategyパターンを使用する場合

0行

既存のソースコード(Human, ~Strategyクラス)は変更されません 。

新規に以下のクラスが追加されるだけです。

class StrategyBachiri
  def self.attack
    puts 'たまにはかいふくするぜ!'
  end
end

このクラスを使用するときは、先ほどと同じように

hero.change_strategy(StrategyBachiri)
hero.action

とするだけです。

まとめ

strategyパターンを使用することにより、戦略をクラスとして分離することが出来ます。

新しい戦略を追加した際に、既存のソースコード 一切変更せずに すみ、

新しい状態のクラスを追加するだけですみます。

ちなみにやっていることはstateパターンと同じです。

違うのは状態か戦略かという違い。

個人的には呼び方を一緒にしてしまってもいい気がします。。

劇的ビフォ◯アフターで学ぶデザインパターン(strategy編:before)

このシリーズについて

このシリーズでは、

について、

仕様変更があった時、既存コードの書き換えがどれくらい発生するか?

という観点で比較し、デザインパターンのメリットをより感じましょ〜というシリーズです。

今回は、「strategy」パターンについてやっていきます。

作戦を変えて戦う勇者クラス

f:id:karoten512:20171021132629j:plain

ドラゴンク◯ストは関係ありません。

作戦を変えて戦う勇者クラスがあったとします。

class Hero

  attr_accessor :strategy_code

  # ガンガンいこうぜ
  STRATEGY_CODE_GANGAN         = 0 

  # じゅもんつかうな
  STRATEGY_CODE_DONT_USE_JUMON = 1 

  # いのちだいじに
  STRATEGY_CODE_INOCHI_DAIJINI = 2 


  def initialize(strategy_code = STRATEGY_CODE_GANGAN)
    # 引数が渡ってきていない場合、デフォルトでガンガンいこうぜ
    @strategy_code = strategy_code
  end 

  # 攻撃させる
  def action
    if strategy_code == STRATEGY_CODE_GANGAN
      # 攻撃魔法のみつかう
      puts 'こうげきまほうをつかった!'
    elsif strategy_code == STRATEGY_CODE_DONT_USE_JUMON
      # 通常攻撃のみ
      puts 'ふつうにこうげきした!'
    elsif strategy_code == STRATEGY_CODE_INOCHI_DAIJINI
      # 回復魔法のみつかう
      puts 'かいふくまほうをつかった!'
    end 
  end 

  # ガンガンいこうぜに切り替え
  def change_strategy_gangan
    @strategy_code = STRATEGY_CODE_GANGAN
  end 

  # じゅもんつかうなに切り替え
  def change_strategy_dont_use_jumon
    @strategy_code = STRATEGY_CODE_DONT_USE_JUMON
  end 

  # いのちだいじにに切り替え
  def change_strategy_inochi_daijini
    @strategy_code = STRATEGY_CODE_INOCHI_DAIJINI
  end 

end

クラスの利用側

hero = Hero.new
hero.action

hero.change_strategy_dont_use_jumon
hero.action

hero.change_strategy_inochi_daijini
hero.action

結果

こうげきまほうをつかった!
ふつうにこうげきした!
かいふくまほうをつかった!

仕様変更

上司

ガンガンいこうぜ だと死ぬこともあるからさ、

 適度に回復してくれる『バッチリがんばれ』を作戦に追加な」

えぇ〜。

。。。このとき想定される変更箇所は、

class Hero

  attr_accessor :strategy_code

  # ガンガンいこうぜ
  STRATEGY_CODE_GANGAN         = 0 

  # じゅもんつかうな
  STRATEGY_CODE_DONT_USE_JUMON = 1 

  # いのちだいじに
  STRATEGY_CODE_INOCHI_DAIJINI = 2 

  ############### 1. 作戦コードの追加 ###############


  def initialize(strategy_code = STRATEGY_CODE_GANGAN)
    # 引数が渡ってきていない場合、デフォルトでガンガンいこうぜ
    @strategy_code = strategy_code
  end 

  # 挨拶をさせる
  def action
    if strategy_code == STRATEGY_CODE_GANGAN
      # 攻撃魔法のみつかう
      puts 'こうげきまほうをつかった!'
    elsif strategy_code == STRATEGY_CODE_DONT_USE_JUMON
      # 通常攻撃のみ
      puts 'ふつうにこうげきした!'
    elsif strategy_code == STRATEGY_CODE_INOCHI_DAIJINI
      # 回復魔法のみつかう
      puts 'かいふくまほうをつかった!'
    end 
    ############### 2. 作戦分岐の追加 ###############
  end 

  # ガンガンいこうぜに切り替え
  def change_strategy_gangan
    @strategy_code = STRATEGY_CODE_GANGAN
  end 

  # ガンガンいこうぜに切り替え
  def change_strategy_dont_use_jumon
    @strategy_code = STRATEGY_CODE_DONT_USE_JUMON
  end 

  # ガンガンいこうぜに切り替え
  def change_strategy_inochi_daijini
    @strategy_code = STRATEGY_CODE_INOCHI_DAIJINI
  end 

  ############### 3. 作戦切り替えの追加 ###############


end

3箇所変更の必要があります。

基本的に既存のソースコードを変更は少ないほど良いです。

(規模によっては既存ソースコードの編集はリスクになるから)

このまま追加すると、条件分岐の数が増えてごちゃごちゃしそうです。。。

そこでstrategyパターン

このように、オブジェクトがいくつかの戦略をもち、それを切り替えて使いたいときは、

strategyパターンが有効です。

本当に有効なのか?ということで次回、strategyパターンを適用してみます。

劇的ビフォ◯アフターで学ぶデザインパターン(state編:after)

前回のソースコードをstateパターンを使用して書き換えていく

f:id:karoten512:20171019233939p:plain

前回のソースコードを見てみると、

状態と、その状態の時の振る舞いが描かれていることがわかります。

図の例なら、「HAPPY」「HAPPY時の振る舞い(hello, goodbye)」です。

これを状態クラスとして書き出します。

状態クラス(状態時の振る舞いを持ったクラス)を作成

# たのしい時
class HappyState
  def self.hello
    puts 'hello!!!'
  end

  def self.goodbye
    puts 'goodbye!!!'
  end
end

他の2つの状態についても状態クラスとして書き出します。

# 通常
class NormalState
  def self.hello
    puts 'hello'
  end

  def self.goodbye
    puts 'goodbye'
  end
end


# 風邪引いた時
class ColdState
  def self.hello
    puts 'hello...'
  end

  def self.goodbye
    puts 'goodbye...'
  end
end

Humanクラスが状態クラスを持つようにする

そして、Humanクラスでは先ほどの状態クラスをstateとして保持するようにします。

class Human

  # その人心理的状態
  attr_accessor :state

  def initialize(state = NormalState)
    # 引数が渡ってきていない場合、デフォルトでNormalStateにする
    state = state
  end

  # 挨拶をさせる(stateクラスにメッセージを送る)
  def hello
    state.hello
  end

  # 挨拶をさせる(stateクラスにメッセージを送る)
  def goodbye
    state.goodbye
  end

  # 状態クラスをセットする
  def set_state(state)
    state = state
  end

end

if文による条件分岐がなくなり、スッキリしましたね。

Humanクラス使用時

human = Human.new()
human.hello
human.goodbye

human.set_state(ColdState)
human.hello
human.goodbye

human.set_state(HappyState)
human.hello
human.goodbye

結果

hello
goodbye
hello...
goodbye...
hello!!!
goodbye!!!

仕様変更

さて、これでstateパターンへの書き換えが終了しました。

ここで例の仕様変更です。

上司「恋に落ちた状態も用意してくれ」

stateパターンを使用しない場合、4行の修正が必要になりました。

stateパターンを使用する場合、既存のソースコードはどのように変更されるでしょうか。

結論

既存のソースコード(Human, ~Stateクラス)は変更されません

新規に以下のクラスが追加されるだけです。

# 恋におちた時
class FallInLoveState
  def self.hello
    puts 'hello,  love you!'
  end

  def self.goodbye
    puts "Don't leave me alone..."
  end
end

このクラスを使用するときは、先ほどと同じように

human.set_state(FallInLoveState)
human.hello
human.goodbye

とするだけです。

まとめ

stateパターンを使用することにより、オブジェクトの状態をクラスとして分離することが出来ます。

そうすると、新しい状態を追加した際に、既存のソースコード 一切変更せずに すみ、

新しい状態のクラスを追加するだけですみます。

縮めたのたった4行だけかよ。って思うかもしれません。

たしかにそうです笑

ただ、状態が大量にある場合や複雑な条件分岐が行われる場合、

stateパターンを用いるとソースコードが明快になることが多いです。

劇的ビフォ◯アフターで学ぶデザインパターン(state編:before)

デザインパターンのメリットについて今一度確認したい

業務で使ったり使わなかったりするデザインパターンですが、

なかなかメリットについてつきつめて考えたことがない。

「なんとなくソースコードがきれいになった気がする。。。」で頭がとまってしまっている><

そこで、デザインパターンを利用する前/後を比較することにより

今一度メリットを確かめてみることにしました。

今回はstateパターンで。

心理的状態によっていろんなhelloを返す人間クラス

class Human

  # その人心理的状態
  attr_accessor :state

  # 普通
  STATE_NORMAL = 0
  # 嬉しい時
  STATE_HAPPY  = 1
  # 風邪を引いた時
  STATE_COLD   = 2

  def initialize(state = STATE_NORMAL)
    # 引数が渡ってきていない場合、デフォルトでNORMALにする
    @state = state
  end

  # 挨拶をさせる
  def hello
    if state == STATE_NORMAL
      puts 'hello.'
    elsif state == STATE_HAPPY
      puts 'hello!!!'
    elsif state == STATE_COLD
      puts 'hello...'
    end
  end

 # 挨拶をさせる
  def goodbye  
   if state == STATE_NORMAL
      puts 'goodbye.'
    elsif state == STATE_HAPPY
      puts 'goodbye!!!'
    elsif state == STATE_COLD
      puts 'goodbye...'
    end
  end

  # 何も起きない
  def nothing_happens
    @state = STATE_NORMAL
    puts '(特に何もなかった。)'
  end

  # 試験に通ったから嬉しい
  def pass_a_exam
    @state = STATE_HAPPY
    puts '(試験に通った!)'
  end

  # 風邪を引いて元気がない
  def catch_a_cold
    @state = STATE_COLD
    puts '(風邪を引いた。。)'
  end

end

クラスの利用側

human = Human.new

human.catch_a_cold
human.hello
human.goodbye

human.pass_a_exam
human.hello
human.goodbye

human.nothing_happens
human.hello
human.goodbye

結果

(風邪を引いた。。)
hello...
goodbye...
(試験に通った!)
hello!!!
goodbye!!!
(特に何もなかった。)
hello.
goodbye.

仕様変更

上司「このクラスに、”恋に落ちた状態”を追加してよ」

こんな仕様変更があったときの変更箇所について考えてみる。

class Human

  # その人心理的状態
  attr_accessor :state

  # 普通
  STATE_NORMAL = 0
  # 嬉しい時
  STATE_HAPPY  = 1
  # 風邪を引いた時
  STATE_COLD   = 2
  ############### 1. 状態の追加 ###############

  def initialize(state = STATE_NORMAL)
    # 引数が渡ってきていない場合、デフォルトでNORMALにする
    @state = state
  end

  # 挨拶をさせる
  def hello
    if state == STATE_NORMAL
      puts 'hello.'
    elsif state == STATE_HAPPY
      puts 'hello!!!'
    elsif state == STATE_COLD
      puts 'hello...'
    end
    ############### 2. 分岐の追加 ###############
  end

 # 挨拶をさせる
  def goodbye  
   if state == STATE_NORMAL
      puts 'goodbye.'
    elsif state == STATE_HAPPY
      puts 'goodbye!!!'
    elsif state == STATE_COLD
      puts 'goodbye...'
    end
    ############### 3. 分岐の追加 ###############
  end

  # 何も起きない
  def nothing_happens
    @state = STATE_NORMAL
    puts '(特に何もなかった。)'
  end

  # 試験に通ったから嬉しい
  def pass_a_exam
    @state = STATE_HAPPY
    puts '(試験に通った!)'
  end

  # 風邪を引いて元気がない
  def catch_a_cold
    @state = STATE_COLD
    puts '(風邪を引いた。。)'
  end

   ############### 4. 状態変化の追加 ###############

end

4箇所変更の必要がある。

基本的に既存のソースコードを変更は少ないほど良い。

(規模によっては既存ソースコードの編集はリスクになるから)

stateパターン

このように、クラスが状態をいくつか持つときはstateパターンが有効。

本当に有効なのか?ということで次回、stateパターンを適用してみます。