引き続きお財布プログラムを考える。
財布にまつわるお金のやり取りは以下のようにまとめられる。 最初は「入れるのはお金だけ」で考える。
オブジェクト指向では、 このような性質を持ったものをクラスというくくりで考え、 そのクラスには以下のような値や手続きがあると整理して考える。
これをRubyのコードに直すと以下のようになる。
#!/usr/bin/env ruby class Purse def initialize(start=0) # コンストラクタの定義 # initializeという名前でメソッド定義しておくと、初期化されるときに # 自動的に呼ばれる(それゆえ通常は定義する)。 # 生成時に呼ばれるメソッドのことを「コンストラクタ」という。 # @で始まる変数はインスタンス変数といいクラス内ですべてのメソッドから # 参照できるもの。@がない変数はメソッド間を越えられない。 @fund = start # 引数を指定しなかったら0になる end def add(amount) if amount < 0 STDERR.printf("マイナスはだめよ(%d)\n", amount) else @fund += amount printf("%d円入れました。\n", amount) end end def sub(amount) if amount > @fund STDERR.printf("そんなに入っていません(残高: %d)\n", @fund) else @fund -= amount printf("%d円出しました。\n", amount) end end def howmuch() # 現在の残高を返す @fund end end # ここまでがクラス定義本体
クラス定義も定義しただけでは何も起きず、利用しなければ意味がないのは
メソッド定義と同じである。上の purse.rb
を利用するプログラムを示す。
#!/usr/bin/env ruby require_relative "purse.rb" # 同じディレクトリあるときは require_relative puts "=== 今日からこの財布を使おう。5000円入れておこう: Purse.new(5000)" saifu = Purse.new(5000) # .newで定義したクラスの実体を生成する。 # 生成するときに initialize メソッドが呼ばれる。 puts "=== 1598円の買い物をしよう。" puts "=== 2000円出すぞ: saifu.sub(2000)" saifu.sub(2000) puts "=== おつりを402円もらった。しまおう: saifu.add(402)" saifu.add(402) puts "=== いまいくらあるんだろう: saifu.howmuch" printf("残り%d円です\n", saifu.howmuch)
実行例を示す。
./purse-use.rb
=== 今日からこの財布を使おう。5000円入れておこう: Purse.new(5000)
=== 1598円の買い物をしよう。
=== 2000円出すぞ: saifu.sub(2000)
2000円出しました。
=== おつりを402円もらった。しまおう: saifu.add(402)
402円入れました。
=== いまいくらあるんだろう: saifu.howmuch
残り3402円です
ここで示した財布は「お金だけ出し入れできる」財布で、 そのようなものはいくらでも作れる。ある性質の集合体を設計したものを クラスといい、製品の仕様書に相当する。
クラスは単なる仕様書だが、そこから実際に動くものとして 生み出されたもののことをインスタンス(実体) といい、実際に作られた製品に相当する。一つの仕様書から 需要に応じてたくさんの製品が作られるように、一つのクラスから いくらでもインスタンスを作ることができる。プログラムで 実際に動くものとして利用するのはインスタンスである。 インスタンスをプログラム中で保持する値のことをオブジェクト という。
上のプログラム purse-base.rb
の後半の意味を示す。
saifu = Purse.new(5000)
定義したPurseクラスの性質を持つインスタンスを一つ生成する(.new)。このとき引数として5000が渡され、それがクラス定義にある
initialize
メソッドに渡される。残高変数
@fund
に5000が格納された状態でこの財布の利用が始まる。
saifu.sub(2000)
saifu
変数にはPurseクラスから生まれたオブジェクトが
入っている。このオブジェクト内にあるsub
メソッドを呼ぶ。
saifu.add(402)
同様にオブジェクト内にあるadd
メソッドを呼ぶ。
売り物の財布なら、同じ財布を持っている人は世の中にたくさんいる。 同じものではあるが、中に入っているものはそれぞれ違う。 人間も、全人類同じゲノムから生まれるが、一人として同じ人は存在しない。 一つのクラスから生まれるインスタンスはすべて違う個体で、独立した 情報を持ち、それに従った動きをする。
chiharu_saifu = Purse.new(5000) # 千春さんが財布を新しくして5000円から開始 hibiki_saifu = Purse.new(8000) # 響さんが財布を新しくして8000円から開始 yu_saifu = Purse.new(1000) # 優さんが財布を新しくして1000円から開始
いずれも、全く同じ設計の財布だが、別個体として存在し続ける。
chiharu_saifu | |
@fund 変数 | 5000 (残高) |
add メソッド | 入れる処理 |
sub メソッド | 出す処理 |
howmuch メソッド | 残高を返す処理 |
hibiki_saifu | |
@fund 変数 | 8000 (残高) |
add メソッド | 入れる処理 |
sub メソッド | 出す処理 |
howmuch メソッド | 残高を返す処理 |
yu_saifu | |
@fund 変数 | 1000 (残高) |
add メソッド | 入れる処理 |
sub メソッド | 出す処理 |
howmuch メソッド | 残高を返す処理 |
これまで使った配列(Array)やハッシュ(Hash)も同様で、 Ruby本体で定義されたクラスを我々は色々な変数に代入して利用している。
クラス定義(classからendまで)の内側で使用された変数はその外側からは 一切見えない。これを変数の隠蔽 といい、プログラム同士が 同じ変数名やメソッド名(まとめてシンボルという)で競合して異常動作を する可能性をなくせる。シンボルを隠蔽してプログラムの一定の部分を独立性を 高めることをカプセル化といい、複数人、 あるいは将来別のプログラムから利用することを見越したプログラムを 作るときには欠かせない。
既に定義されたクラスの基本機能は踏襲しつつ、 機能や性質を追加したい場合に継承 を使う。
たとえば、Purseクラスは現金を入れることしか想定していない。 この機能を保持したまま、「カードも入れられるようにしたい」とする場合、 同じようにクラス設計すると効率が悪い。
require_relative "purse.rb" # Purseクラスの定義を読み込む class PurseWithCard < Purse # Purseを親として引き継いでクラス定義 def initialize(money=0, card=[]) # コンストラクタ(.newで呼ばれる) super(monkey) # 親(Purse)の同じ名前のメソッド(initialize)を呼ぶ @card = card # 独自のインスタンス変数を導入 end def putinCard(card) @card << card printf("%s をしまいました。\n", card) end def drawCard(card) @card.delete(card) printf("%s を取り出しました。\n", card) end def myCards() @card.join(",") end end
カードを複数枚配列で保持できるインスタンス変数 @card
、
カードの出し入れとリスト返す処理をする3つのメソッド、
putinCard, drawCard, myCards
が追加される。
このように、あるクラスを拡張して別のクラスを作ることで、 徐々に複雑さを増した設計をすることができる。
上記のクラス PurseWithCard
を利用したプログラム例を示す。
実行例:
./purse-inherit.rb
=== 今日からこの財布を使おう。5000円入れておこう: Purse.new(5000)
=== 1598円の買い物をしよう。
=== 2000円出すぞ: saifu.sub(2000)
2000円出しました。
=== おつりを402円もらった。しまおう: saifu.add(402)
402円入れました。
=== いまいくらあるんだろう: saifu.howmuch
残り3402円です
=== 次は4000円のものをカードで買おう!
=== このお店は手持ちのカードで買えますか: Pizza,Mustard
=== じゃあPizzaカードで!
Pizza を取り出しました。
=== じゃあPizzaカードで!
=== やったー、手持ちが足りないのに買えた! 帰ろう。
...
=== ただいま。あ、カードがない! 店に忘れた。
Mustard