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はサブクラスからも呼び出せる
PHPとRubyの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を
正直ビビりました。
劇的ビフォ◯アフターで学ぶデザインパターン(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」パターンについてやっていきます。
作戦を変えて戦う勇者クラス
ドラゴンク◯ストは関係ありません。
作戦を変えて戦う勇者クラスがあったとします。
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パターンを使用して書き換えていく
前回のソースコードを見てみると、
状態と、その状態の時の振る舞いが描かれていることがわかります。
図の例なら、「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パターンを適用してみます。