[devel][racket] Racketのクラスの基礎っぽいことのメモ(2) 継承とか
その前に大事なことを書き忘れた。
Racketのオブジェクトシステムには、javaやC++のような明示的なコンストラクタメソッドがない。
クラスのフィールドの宣言などは、オブジェクト生成時に、上から順番に評価されるので、そのことが実質的なコンストラクタの働きをする、ということになる、ということらしい。
そう言えば、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がなんやらとかあるみたいけど、どうしようか、という感じ。