レコードとオブジェクト
レコード 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 => 7 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 ( もちろん include(math) が必要 ) 実は 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) 2 点間の差分ベクトルの x,y の大きい数 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 曲線を n 等分する prev から r まで線分を描く bezier.rb
練習 授業の Web ページから point.rb,line.rb, bezier.rb, kana.rb をダウンロードして 練習 8.1 a) の図形 kana() を描画してみなさい (abs.rb, max.rb, make1d.rb, make2d.rb などは すでに作成したものである )
進捗状況の確認 1. kana() が表示できた ( できた時点で投票してください ) できた人は 教科書 168 ページの練習 8.2 に挑戦してみてください
Line や Bezier もレコードにできる class Line attr_accessor("p0", "p1") class Bezier attr_accessor("p0", "c", "p1")
load("./max.rb") load("./abs.rb") def line_draw(l, a) 2 点間の差分ベクトルの x,y の大きい数 n=max(abs(l.p1.x l.p0.x), abs(l.p1.y l.p0.y)) for i in 0..n point_draw(point_interpolate(l.p0,l.p1,i*1.0/n), a) line.rb
レコードの意義 一まとまりの複数のデータを表す変数が 1 個ですむ Point の場合,x 座標と y 座標をまとめて扱える Line の場合, 始点と終点をまとめて扱える 4 つの座標をまとめて扱えている Bezier の場合, 始点と終点, 第 3 点をまとめて扱える 6 つの座標をまとめて扱えている このようなデータのまとまりを値として扱える 関数の値として返すことができる 4 つの座標を一度に返すことは通常の関数では難しい
しかし, レコードのままだと クラス ( レコードの種類 ) ごとに操作を定義 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 のレコードに, スカラー倍, ベクトル和などの操作を覚えさせる クラス定義の中で操作法 ( メソッド ) を定義する カプセル化 (encapsulation) 継承 (inheritance) 多相性 (polymorphism)
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> 新しいオブジェクト
練習 授業のページから oo-point.rb をダウンロードして 練習 8.5 b), c) を追加したら line_draw.rb で直線を描画してみなさい 進捗状況の確認 1. 直線が描画できた時点で投票してください その後 練習 8.5 a), d) も定義してください
isrb(main):001:0> load("line_draw.rb") true isrb(main):002:0> load("make2d.rb") true isrb(main):003:0> a=make2d(200,200) : ( 省略 ) isrb(main):004:0> show(a) : ( 省略 ) isrb(main):005:0> line_draw(point.new(50,50),point.new(100,170),a) 0..120 isrb(main):006:0> line_draw(point.new(40,150),point.new(180,70),a) 0..140
進捗状況の確認 1. 直線が描画できた時点で投票してください 2. drawメソッドはできた 3. interpolateメソッドはできた 4. どちらもできていない
( 抽象的な ) クラス Figure load("oo-point.rb") load("line_draw.rb") class Figure def draw(a) pnts = self.points() for i in 0..(pnts.length()-2) line_draw(pnts[i], pnts[i+1], a) draw メソッドの定義 Figure の種類によらず points メソッドで点列が得られる カプセル化 多相性 oo- figure.rb
load("oo-figure.rb") class Line < Figure attr_accessor("p0", "p1") Figure オブジェクトの継承 draw メソッドを使える 継承 def initialize(q,r) self.p0 = q self.p1 = r def points() [self.p0, self.p1] points メソッドは両端点を返す カプセル化 oo- line.rb
load("oo-line.rb") class Bezier < Line attr_accessor("p0", "c", "p1") def initialize(q,r,s) super(q,s) self.c = r def points() n = 16 pnts = Array.new(n+1) pnts[0] = self.p0 pnts[n] = self.p1 for i in 1..(n-1) t = i*1.0/n q0 = self.p0.interpolate(c, t) q1 = self.c. interpolate(p1, t) pnts[i] = q0.interpolate(q1, t) pnts Line オブジェクトの継承 draw メソッド (ini@alize メソッド ) Line クラスの ini@alize メソッドを呼び出す points メソッドは t=0 1 の点列 (16+1 個 ) カプセル化 最初と最後の点は self.p0 と self.p1 線形補間を 2 段階して曲線上の点を求める 点列を返す oo- line.rb
練習 授業のページから oo-figure.rb, oo-line.rb, oo-bezier.rb, drawall.rb をダウンロードして drawline() と drawground() を実行してみなさい 進捗状況の確認 1. drawground() ができたら投票してください その後は oo-circle.rb を完成させて drawmoon() を実行してください ただし 練習 8.5 d) の rotate メソッドが必要です
カプセル化, 継承, 多相性 カプセル化 もの を表すデータとメソッドをまとめ, 決められたインスタンスメソッドを通してのみインスタンス変数にアクセスをさせ, インスタンス変数に直接触らせないこと 継承 他のクラスのインスタンス変数やメソッドを受け継いで新しいクラスを作ること新しいクラスをサブクラス, 元のクラスをスーパークラスと呼ぶ 多相性 同じ名前のインスタンスメソッドを呼び出しても, 適用されたオブジェクトによって異なるメソッドを呼び出すこと実行時にメソッドを探す動的結合で実現する