proglog

主にプログラミングに関する断片的メモ

[devel][racket] Racketのクラスの基礎っぽいことのメモ(2) 継承とか

その前に大事なことを書き忘れた。
Racketのオブジェクトシステムには、javaC++のような明示的なコンストラクタメソッドがない。
クラスのフィールドの宣言などは、オブジェクト生成時に、上から順番に評価されるので、そのことが実質的なコンストラクタの働きをする、ということになる、ということらしい。
そう言えば、Gaucheのオブジェクトシステムも、明示的なコンストラクタがなかったような。
Lisp系ってこういう風なのかな。


で、本題。
メソッドに関して、いわゆるオーバーライドとaugment(拡張、付加)という概念が登場する。

継承

フィールドの継承

まずは、シンプルに。

(define exclass2%
  (class* object% ()
    (super-new)
    (init-field [pblcfield1 10] [pblcfield2 20])))

(define exclass2-1%
  (class* exclass2% ()
    (super-new)
    (inherit-field pblcfield1)
    (define/public (getsf1)
      ;直接アクセス
      pblcfield1)
    (define/public (getsf2)
      ;間接的にはアクセスできる
      (get-field pblcfield2 this))))

(send (new exclass2-1%) getsf1)
;;;>10
(send (new exclass2-1%) getsf2)
;;;>20

サブクラス内で、(inherit-field フィールド名)を使って、使用したいパブリックなフィールドを指定する。
すると、そのサブクラス内で、ダイレクトにそのフィールドにアクセスすることができる。
ここでは、pblcfield1。


一方、この指定をしていないパブリックフィールドも、(get-field フィールド名 this)を使えば、間接的にアクセスできる。
ダイレクトアクセスができないだけで、当然継承はされてるので。
ここでは、pblcfield2
thisはいわゆるthis。


プライベートフィールドにはダイレクトアクセスはできない。
自前のアクセサを定義してあれば、別だけど。


javaのprotectedに当たる仕組みはないっぽい。

メソッドの継承

(define exclass3%
  (class* object% ()
    (super-new)
    (init-field [pblcfield1 "pubfield1"] [pblcfield2 "pubfiled2"])
    (define/public (smethod1)
      (string-append  pblcfield1 " - " pblcfield2))
    (define/public (smethod2)
      (string-append  pblcfield2 " - " pblcfield1))))

(define exclass3-1%
  (class* exclass3% ()
    (super-new)
    (inherit smethod1)
    (define/public (method1)
      ;直接
      (smethod1))
    (define/public (method2 marg2)
      ;間接
      (string-append marg2 (send this smethod2)))))

(send (new exclass3-1%) method1)
;;;>"pubfield1 - pubfiled2"

(send (new exclass3-1%) method2 "marg2 - ")
;;;>"marg2 - pubfiled2 - pubfield1"

(inherit メソッド名)で継承したいメソッドを指定する。
すると、ダイレクトにそのメソッド、ここではsmethod1にアクセスできる。


一方、指定していないメソッドも、外部での呼び出しと同じく(
send this メソッド名)で呼び出すことができる。


オーバーライド

define/publicで宣言されたメソッドをオーバーライドすることができる。
そしてそれには、そのオーバーライドを行うメソッドをdefine/overrideで宣言する。
ここではsmethod1。


そしてそのオーバーライドを行うメソッドの中で、スーパークラスのメソッドを呼び出して利用することができる。
(super メソッド名)という形になる。
ここでは、smethod2。

(define exclass3-2%
  (class* exclass3% ()
    (super-new)
    (define/override (smethod1 marg1)
      ;全く上書き
      marg1)
    (define/override (smethod2)
      ;スーパークラスのメソッドを利用
      (string-append "override - " (super smethod2)))))

(send (new exclass3-2%) smethod1 1)
;;;>1
;;;>"override - pubfiled2 - pubfield1"
(send (new exclass3-2%) smethod2)

smethod1は数値を返すメソッドに全く上書き。
smethod2は、スーパークラスのsmethod2の出力を利用している。


当然、このオーバーライドしているサブクラスのメソッドを、そのサブクラスでオーバーライドすることができる。
この連鎖を止めるには、define/override-finalでメソッドを宣言する。
あるいは、最初からオーバーライド禁止でdefine/public-finalで宣言する。

拡張(augmentation)

augmentationをどう訳していいのか分からないので、ここでは拡張とした。
増強とか付加の方がいいかもしれない。
要は、サブクラスのメソッドを、スーパークラスで呼び出す。
superを使ったオーバーライドと逆の形。

スーパークラスのメソッドに、サブクラスのメソッドを添加するイメージ。
emacs lispで言うとスーパークラスのメソッドにhookを用意する感じ。



この仕組みを使うには、スーパークラスのメソッドをdefine/pubmentで宣言し、添加を行うサブクラスのメソッドをdefine/augmentで宣言する。

(define exclass4%
  (class* object% ()
    (super-new)
    (define/pubment (smethod1)
      (string-append "SuperClass" (inner "" smethod1)))))

(send (new exclass4%) smethod1)
;;;>"SuperClass"

(define exclass4-1%
  (class* exclass4% ()
    (super-new)
    (define/augment (smethod1)
      (string-append " - SubClass" (inner "" smethod1)))))

(send (new exclass4-1%) smethod1)
;;;"SuperClass - SubClass"

スーパークラスの中で(inner デフォルト値 サブクラスのメソッド名)という形で、サブクラスのメソッドを呼び出す。
デフォルト値は、サブクラスで添加メソッドが宣言されない時に使われる。
(void)という形をよく見た。


ここでは、サブクラスのメソッドの出力を、スーパークラスのメソッドの中で文字列連結の形で利用してる。


そして、さらなるサブクラスでの付加も可能なので、この添加メソッドの中でもinnerを使用している。


ファクトリメソッドなんかを作る時のvirtualとかabstractの代わりって感じか。


この添加連鎖を止めるには、単にinnerを使わない、だけ。

(define exclass4-4%
  (class* exclass4-1% ()
    (super-new)
    (define/augment (smethod1)
      (string-append " - use no inner"))))

(send (new exclass4-4%) smethod1)
;;;>"SuperClass - SubClass - use no inner"

(define exclass4-5%
  (class* exclass4-4% ()
    (super-new)
    (define/augment (smethod1)
      (string-append " - how about me?"))))

;効いてない
(send (new exclass4-5%) smethod1)
;;;>"SuperClass - SubClass - use no inner"

別にエラーにもならないけど、何も効いてない。

オーバーライドメソッドにaugmentする。

この辺からややこしさが増す。
define/publicなメソッドをオーバーライドするんだけど、同時に、サブクラスで、augmentして欲しい、そんなあなたにoverment

(define exclass3-3%
  (class* exclass3-2% ()
    (super-new)
    (define/overment (smethod1 marg1)
      (string-append  "super:" marg1
                      (inner "default" smethod1 (string-append  "marg1:" marg1 ))))))

(send (new exclass3-3%) smethod1 "sarg3 - ")
;;;>"super:sarg3 - default"

(define exclass3-4%
  (class* exclass3-3% ()
    (super-new)
    (define/augment-final (smethod1 marg1)
      (string-append " - augment - " marg1))))

(send (new exclass3-4%) smethod1 "sendarg4")
;;;>"super:sendarg4 - augment - marg1:sendarg4"

オーバーライドの例で使ったクラスexclass3-2% を継承し、exclass3-3% を宣言する。
ここで、define/overmentを使う。
つまり、overrideするし、自分のサブクラスからaugmentもして欲しい。
だから当然、innerも使う。


クラスexclass3-2% に対するオーバーライドとしては、単純にオーバーライドするだけで、superは使ってない。
ただ、次に宣言するexclass3-4% に対してスーパークラスであることを示すために"super:"という文字列を出力している。


クラスexclass3-4% がaugmentを行う。
ここでは、define/augment-finalを使っている。
ここでストップのサイン。


出力は、単純に、exclass3-4% による添加が行われた形になっている。

添加をオーバーライドする

おんなじ継承の階層に兄弟クラスをわざわざ作るんじゃなくて、継承させて、添加部分だけを書き換えたい、そんなときだろうか、このdefine/augride

(define exclass4-2%
  (class* exclass4-1% ()
    (super-new)
    (define/augride (smethod1)
      (string-append " - augride"))))

(send (new exclass4-2%) smethod1)
;;;>"SuperClass - SubClass - augride"


(define exclass4-3%
  (class* exclass4-2% ()
    (super-new)
    (define/override-final (smethod1)
      (string-append " - override-final"))))


;拡張部分がオーバーライドされている。
(send (new exclass4-3%) smethod1)
;;;>"SuperClass - SubClass - override-final"

拡張の最初の例でサブクラスとして使ったexclass4-1% を継承してexclass4-2% を宣言する。
ここで、exclass4-1% に対して添加、拡張、augmentを行うけど、overrideも許可するので、augrideを使う。


出力は通常のaugment通り、 "- augride"が添加されたものになっている。


次に、これを継承してexclass4-3% を宣言する。
ここではdefine/override-finalを使ってオーバーライドしている。
これは前述のとおり、これ以上のオーバーライドを禁止するもの。


出力は、直接のスーパークラスであるexclass4-2% が行ったaugment、添加部分だけがオーバーライドされて、" - override-final"だけが、付加されたものになっている。
つまり、メソッド全体がオーバーライドされるわけではない、ということを表している。


とりあえず、こんなもの。
あと、名前の中と外とか、genericがどうのこうのとか、mixin、traitとか、contractがなんやらとかあるみたいけど、どうしようか、という感じ。