お題:class化
チャート式Rubyの最終6回目,オブジェクト指向です.このキーとなる考え方が,
- 隠蔽(capsulation)
- 継承(inheritance)
- 多形(polymorphism)
です.methodにするのはある種の隠蔽なんですが,さらに言語のシステムとして徹底したのが,classです.
解法:最初のclass
前にやったhello.rbを少し拡張したのをみてください.
def puts_hello name
puts "Hello #{name}."
end
def gets_name
name = ARGV[0] || 'world'
return name
end
name = gets_name
puts_hello name
main loopがありますよね.「こいつも消せないか?」というのがclassの発想です.最終的には以下の通りできるんですが,これをrefactoringしましょう.
class Greeter
def initialize
@name = gets_name
puts_hello
end
def puts_hello #salute
puts "Hello #{@name}."
end
def gets_name
name = ARGV[0] || 'world'
return name.capitalize
end
end
Greeter.new
> ruby hello_class.rb
としてみてください.同じでしょ.同じなんですよ.
- Greeter.new
- class Greeter
- initialize method
- @name
なんかがkeyになります.
さらにfoldingとかtoggleの機能を使うと見やすくなります.
解説(指向):method的かobject思考的か
methodとclassを比べるとオブジェクト指向の書き方の流儀の違いが実感できます.method名をverbとすると,
- methodでは,verb(subject, object)
- object指向では, subject.verb(object)
と読めて,英語の文法に近いので,object志向がいいと良く言われます.
サンプルコードを見てください.上から,method, 継承を使ったclass, classへの 上乗せ 上書き(override monkey patching)で書いてみました.
require 'colorize'
# method
def hello(name)
"Hello #{name}."
end
# inherited class
class Greeter < String
def hello
"Hello #{self}."
end
end
# extend class
class String
def hello
"Hello #{self}."
end
end
# method call
name = ARGV[0]
puts hello(name).green
# inherited class call
greeter = Greeter.new(ARGV[0])
puts greeter.hello.green
# extend class call, override
puts ARGV[0].hello.green
最後にある通り,
ARGV[0].hello.green
ではSVO的な発想ができないですが,それでも,順番に振る舞いを付け加えていけるのは直感的です.methodだと,一番内側から順に外側へ向かって,括弧で括りながら書いていくんで,書く時はいいんですが,後で読む時に苦労します.object指向は,読む方だけでなく,慣れてくるとあてずっぽうでcodeが書けるようになりますんで,書く方にも優しいんですよ.
「上乗せ」って「ただ乗り」みたいで,ラクそうですし,上書きも可能です.これらは総称してmonkey patchingとか呼ばれます.あまり良くないって...でも...
解説(付録):attr_accessorとか
rubyのclassのidiom(慣用句)でよくあるのが,
1 class Greeter
2 attr_accessor :name
3 def initialize(name='world')
4 @name = name
5 end
6
7 def hello
8 puts "Hello #{@name.capitalize}."
9 end
10 end
11
12 greeter = Greeter.new()
13 greeter.hello
14 greeter.name = ARGV[0]
15 greeter.hello
これを動かしてみると
> ruby hello_class_accessor.rb bob
Hello World.
Hello Bob.
どうなっているか予測できますか?予測できれば相当な実力がついています.accessorとかdefault代入とかの意味がわかっているということ.
main loopの操作を順番に説明すると
- (L12)Helloというclassを引数なしでnewしてworldと名付けた.
- (L3)nameはdefaultでは'world'
- (L4)これをinstance(子供)変数の@nameに入れた
- (L13)次にhelloメソッドを呼んだ
- (L8)'Hello world.'
- (L14)world.nameにARGV[0]='bob'を代入した
- (L2)attr_accessorに:nameがあるので,代入される
- attr_accessorは以下のmethodsを作っているのと等価
- (L15)helloメソッドを呼んだ
- (L8)'Hello bob.'
def name # reader(getter)
return @name
end
def name=(new_name) # writer(setter)
@name = new_name
end
getter, setterを一度に作るのがattr_accessorで,どちらかだけのときには,attr_reader, attr_writerを使います.
あとは,privateとかprotectedで,methodのaccess制限します.defaultはpublicなんですが,デメテルの箱入り娘は隠しておくべきとか...
言い忘れた一番大事なこと
classの「隠蔽,継承」とそれに関連する諸注意でした.
えっと,一番大事なことを伝えるのを忘れてました.変数の名前とかclassの構成とかはcodeを書くまえにあまり悩まないでください.掟ってのは,そのうちに身についてきます.考えてなんとかなるものではないんで.「下手の考え休みに似たり」です.下手がいくら考えても答えは出てきません.
それよりも動くcodeをガンガン書いて行ってください.どっちみち必要な動作はどっかで書く必要があります.置き場所とか名前とかは,ここまでで示した通り,あとで移動させたり変更したりするものであって,初めから決まっているものではありません.設計よりも実装です.
ただ,見やすくするためには「classまでもっていくべき」ということだけは意識してください.Rubular の所でも強調しましたが,構成要素ってのは意外と限られています.classを書くときに必要なidiomって,突き詰めると,ここに書いたことぐらいで,あとはそれらの組み合わせです.これらを駆使して,オブジェクト思考の「隠蔽,継承,多形」がうまくいくとほんとうに綺麗ですから.どうすれば綺麗にできるかの指針がRefactoringと名づけられたstepです.こいつにはMartin Fowlerの良書1があります.第一章をやるだけで,「多形」とDesign patternの必要性を実感できます.
発展問題
- assert_equalをclass化して,Integerに
overridemonkey patching しなさい.- 3.assert_equal 3でtrueが返るようになります.
- selfで3とかが返るはず.
- moduleをincludeする方が筋が良さそうですが...自信ありません.
参考資料
- source ~/git_hub/ruby_docs/chart_style_ruby/c6_class_hello.org
-
リファクタリング:Rubyエディション, ジェイ・フィールズ , シェーン・ハービー , マーティン・ファウラー , 長尾 高弘, アスキー・メディアワークス (2010/2/27). ↩