レコードとオブジェクト
レコード class Point attr_accessor("x", "y") インスタンス変数の宣言 point.rb
irb(main):004:0> load("point.rb") => true irb(main):005:0> p = Point.new() => #<Point:0x40332080> irb(main):006:0> p.x = 3 => 3 irb(main):007:0> p.y = 4 => 4 irb(main):008:0> p => #<Point:0 x40332080 @y=4, @x=3> オブジェクト (Point クラスのインスタンス ) インスタンス変数への代入
irb(main):013:0> p.y => 4 インスタンス変数の参照 irb(main):014:0> sqrt(p.x**2 + p.y**2) => 5.0 Rubyでは, レコードもオブジェクトの一種 レコードのクラスは, インスタンス変数の宣言のみから成る
def point_make(u,v) p = Point.new() p.x = u p.y = v p オブジェクト ( への参照 ) を返す point.rb
def point_scale(p,s) point_make(p.x*s, p.y*s) def point_add(p,q) point_make(p.x+q.x, p.y+q.y) point.rb
def point_interpolate(p,q,t) point_add(point_scale(p,1-t), point_scale(q,t)) def point_draw(p,a) if 0 <= p.y+0.5 && p.y+0.5 < a.length() && 0 <= p.x+0.5 && p.x+0.5 < a[0].length() a[p.y+0.5][p.x+0.5]=1 Ruby の配列の添え字が実数でもいいことを利用している 配列 a の縦の長さ 配列 a の横の長さ 0.5 は四捨五入をするため point.rb
load("./max.rb") load("./abs.rb") def line_draw(p0,p1,a) n=max(abs(p1.x - p0.x), abs(p1.y - p0.y)) for i in 0..n point_draw(point_interpolate(p0,p1,i*1.0/n), a) line.rb
def bezier_draw(p0,c,p1,a) n = 10 prev = p0 for i in 1..n t = i*1.0/n q0 = point_interpolate(p0, c, t) q1 = point_interpolate(c, p1, t) r = point_interpolate(q0, q1, t) line_draw(prev, r, a) prev = r bezier.rb
練習 max.rb と abs.rb を用意し point.rb line.rb bezier.rb をロード make2d も定義 以下を順に isrb のもとで実行せよ p0 = point_make(10,10) p1 = point_make(90,90) c = point_make(10,90) a = make2d(100,100) bezier_draw(p0,c,p1,a) show(a)
もっと, レコード class Line attr_accessor("p0", "p1") class Bezier attr_accessor("p0", "c", "p1")
レコードの意義 一まとまりの複数のデータを表す変数が 1 個ですむ Point の場合,x 座標と y 座標をまとめて扱える Line の場合, 始点と終点をまとめて扱える 4 つの座標をまとめて扱えている Bezier の場合, 始点と終点, 第 3 点をまとめて扱える 6 つの座標をまとめて扱えている このようなデータのまとまりを値として扱える 関数の値として返すことができる 4 つの座標を返すことは通常の関数では難しい 内部のデータがどうなっているかを知らなくても使うことができる もし,Point の座標系が極座標系だったとしても, 関数の使い方に変更を加える必要がない
しかし, レコードのままだと クラス ( レコードの種類 ) ごとに操作を定義 Pointクラス Lineクラス Bezierクラス point_draw line_draw bezier_draw これらが混在している場合, if figureがpointクラスのオブジェクト point_draw(figure,a) else if figureがlineクラスのオブジェクト line_draw(figure,a) else if figureがbezierクラスのオブジェクト bezier_draw(figure,a)
オブジェクト指向では オブジェクト ( ここではレコードのこと ) に自分に対する操作の仕方を覚えさせる Pointのレコードに, スカラー倍, ベクトル和などの操作を覚えさせる 自分の操作法を知っているレコードをオブジェクトと呼ぶ それぞれのPointオブジェクトが操作法を抱え込むのは大変なので,Pointオブジェクトを,Pointクラスにまとめ, 親分 のPointクラスがPointオブジェクトの作り方や操作法を一括管理する
レコードからオブジェクトへ メッセージとも言う オブジェクト 内部データ構造 + 処理手続き レコードの変数にアクセスするのと同じ形での質問や動作の依頼 隠蔽されている ( カプセル化 ) それに対する応答 実は, オブジェクトのクラスがこれらを知っている 個々のオブジェクトは自分の具体的な値を知っている
class Point attr_accessor("x", "y") def initialize(u,v) self.x = u self.y = v 自分自身の x @x でもよい 初期化メソッド クラス def scale(s) Point.new(self.x * s, self.y * s) def add(q) Point.new(self.x + q.x, self.y + q.y) scale メソッド 自分の操作法をメソッドと呼ぶ oo-point.rb
irb(main):005:0> p = Point.new(3,4) 初期化メソッドの引数 => #<Point:0x7ffa3ed8 @x=3, @y=4> irb(main):006:0> p.x => 3 irb(main):007:0> q = p.scale(2) => #<Point:0x7ffa3e84 @x=6, @y=8> irb(main):008:0> p.add(q) => #<Point:0x7ff9b0f8 @x=9, @y=12> irb(main):009:0> p.add(q).scale(0.5) => #<Point:0x7ff94ca8 @x=4.5, @y=6.0> 新しいオブジェクト 新しいオブジェクト 新しいオブジェクト
Pointクラスに以下のメソッドを追加 def move(x,y) self.x = x self.y = y irb(main):002:0> p = Point.new(3,4) => #<Point:0x7ffa32bc @y=4, @x=3> irb(main):003:0> p.move(4,6) => 6 irb(main):004:0> p => #<Point:0x7ffa32bc @y=6, @x=4>
class Line attr_accessor("p0", "p1") def initialize(q,r) self.p0 = q self.p1 = r def draw(a) n = max(abs(self.p1.x - self.p0.x), abs(self.p1.y - self.p0.y)) for i in 0..n p = self.p0.interpolate(self.p1, i*1.0/n) p.draw(a) oo-line.rb
多相性 def drawall(elements,a) for i in 0..elements.length()-1 elements[i].draw(a) a elements[i] のクラスが何であれ, その draw メソッドが呼ばれる drawall.rb
def drawmoon() p0=point.new(0,85) p2=point.new(99,85) f=[line.new(p0,p2), Bezier.new(p0,Point.new(50,60),p2), Circle.new(Point.new(66,20),20)] a=make2d(100,100) drawall(f,a) show(a) drawall.rb
練習 Point クラスに draw メソッドを追加せよ すなわち oo-point.rb の Point クラスの中に def draw(a) を追加 point.rb の point_draw を参考に interpolate メソッドを追加 ( 次のスライド参照 ) drawmoon() を実行せよ 必要なクラスのファイルをロードする oo-circle.rb は萩谷のページから
def interpolate(q,t) self.scale(1-t).add(q.scale(t)) もしくは def interpolate(q,t) scale(1-t).add(q.scale(t))
進捗状況の確認 1. drawmoon が動いた時点で 投票してください 2. draw メソッドを追加したが drawmoon が動かない 3. draw メソッドができない
提出 drawメソッドもしくは oo-point.rb 全体を is-komaba@lyon.is.s.u-tokyo.ac.jp に送ってください Subject: is-kadai-1-20
class Bezier < Line attr_accessor("p0", "c", "p1") Line クラスを継承 継承 def initialize(q,r,s) super(q,s) self.c = r def turn(theta) super(theta) self.c = self.c.rotate(theta) Line クラスの初期化メソッドを呼び出す Line クラスの turn メソッドを呼び出す oo-bezier.rb
オブジェクト, クラス, インスタンス変数, メソッド オブジェクト コンピュータのなかで もの に対応する " もの " クラス 同じようなオブジェクトをまとめたもの プログラミングでは先にクラスを定義しておき, 必要に応じてクラスに属するオブジェクトを生成する 生成したオブジェクトを, クラスのインスタンスという インスタンス変数 オブジェクトなかでデータを表す要素 レコードではフィールド, もの の視点では属性に対応 インスタンスメソッド オブジェクトの操作を行うためのメソッド メッセージのやり取りはメソッドの呼び出しで代用 メッセージの内容は引数に対応
カプセル化, 継承, 多相性 カプセル化 もの を表すデータとメソッドをまとめ, 決められたインスタンスメソッドを通してのみインスタンス変数にアクセスをさせ, インスタンス変数に直接触らせないこと 継承 他のクラスのインスタンス変数やメソッドを受け継いで新しいクラスを作ること 新しいクラスをサブクラス, 元のクラスをスーパークラスと呼ぶ 多相性 同じ名前のインスタンスメソッドを呼び出しても, 適用されたオブジェクトによって異なるメソッドを呼び出すこと 実行時にメソッドを探す動的結合で実現する
オブジェクト指向プログラミング言語 オブジェクト指向の要素をどのように言語としてまとめるかは, プログラム言語設計者の腕のみせどころであり, いろいろなバリエーションがある ここでは Ruby での書き方を紹介する 一つの言語で書き方を覚えておけば, 他のバリエーションも短時間で使いこなすことができるようになるだろう バリエーションについては第 10 章で少し紹介する