- PR -

あるクラスの paint メソッド内でサブクラスの paint メソッドを呼びたい

投稿者投稿内容
unibon
ぬし
会議室デビュー日: 2002/08/22
投稿数: 1532
お住まい・勤務地: 美人谷        良回答(20pt)
投稿日時: 2003-07-06 11:37
unibon です。こんにちわ。
みなさまありがとうございます。

引用:

かずくんさんの書き込み (2003-07-03 18:13) より:
上述したコードのように、Templete Method パターンで記述する方法ではだめでしょうか?メソッドの名前は変わってしまいますが、同様の事を行えることができると思います。


これは私の2つのサンプルの内の前者の方の、
paint2 メソッドを使うやり方と等価だと思います。
ちょっと、最初に目的を明確に書かなかったのですが、
私は paint メソッドというメソッドは、サブクラスで挙動を再定義できるために
コールバックされるメソッドという位置付けであると理解しているのですが、
そのためのコールバックメソッドが paint と別に paint2/paintsub ができてしまい、
インターフェースが「汚く」なってしまうのを避けたいという思いがあります。
「汚い」というのは、なんと言うんでしょうか、
もともとコールバックの定義のために paint というメソッドがあるのに、
同じ目的のために別のメソッド(paint2/paintsub)を定義しなければいけないのは、
なんだか嫌だ、と言う漠然とした感じです。

引用:

Astmildさんの書き込み (2003-07-03 22:08) より:
ソースコード上、スーパークラスのprinter()がサブクラスのprinting2()を呼んでいるように
見えますが、結果は同じサブクラス内で処理されてました。


この実行結果は、

コード:
PrintSub1クラスで実行中
a
b
!
PrintSub2クラスで実行中
a
c
?


と、なりますが、
"!" は PrintSub1 クラスが出力し、
"?" は PrintSub2 クラスが出力しているので、
それぞれ「別のサブクラス内で処理されている」と言えるはずです。
#もしかしたら言葉の解釈のほうに相違があるのかもしれません。
unibon
ぬし
会議室デビュー日: 2002/08/22
投稿数: 1532
お住まい・勤務地: 美人谷        良回答(20pt)
投稿日時: 2003-07-06 11:56
unibon です。こんにちわ。

引用:

英-Ranさんの書き込み (2003-07-03 23:31) より:
ひとつ理由として思いつくのは、maruさんが言っているように、サブクラスのpaintメソッドでsuper.paintを呼ぶと無限ループに陥ること。でも、それは言語仕様で禁止するという方法もあるわけで……


たしかに、無限ループ(再帰が無限)のようなことが起こりうるようだし、
呼ぼうとしたサブクラスのメソッドが未定義だった場合の対処などが必要となりそうで、
複雑でなんだか良く分からなくなってきました。
ただ、できるにしてもできないにしても、
なにか理論のよりどころはぜひ知りたいと思います。
以下はちょっと冗談っぽくなりますが、以前に見つけた興味深いものとして、
http://homepage3.nifty.com/iromono/hardsf/ctccomp.html
で紹介されているような突飛な理論に、この話題は近いのかなあ、
などと思えてきました。
#そこまでは突飛ではないのですが(笑)。
unibon
ぬし
会議室デビュー日: 2002/08/22
投稿数: 1532
お住まい・勤務地: 美人谷        良回答(20pt)
投稿日時: 2003-07-06 12:50
unibon です。こんにちわ。

引用:

Wataさんの書き込み (2003-07-04 09:59) より:
ども、Wataです。
これは「継承よりもコンポジションを選べ」の格言で上手くいくのでは?


コンポジションだと、メソッドの名前は paint ひとつにまとまりますが、
逆に、インスタンスが2つ必要になると思います。
たとえば、java.io.FilterInputStream などもコンポジションを使った典型例であり、
一般的なソリューションですが、2つのインスタンスが要るのが、
私の好みも混じってしまいますが、なんとなく敬遠してしまいます。
でも、教えていただいた ExpansionCanvas のサンプルなどを拝見すると、
やはり実用的なのはこのやりかたなのかな、と感じます。

引用:

TO-Rさんの書き込み (2003-07-04 11:57) より:
要するにコールバック・プロシージャが欲しいんですよね?

  super#methodを固有処理に挟むのではなく、
  super#methodに固有処理を挟みたい。
  でも前後に呼びを書くのは美しくない。
  …みたいな


そうです。コールバックをいかにキレイに定義するかで悩んでいます。
ただ、思ったのは、Java のメソッドのオーバライドは、
コールバックに使えますが、それ以外に、機能の拡張(文字通り extends)という用途にも使え、
本来は、メソッドオーバライドには意味合いが2つ(やそれより多く)あるのかもしれません。
このあたりを整理すると良いのかな、とも思うのですが、良く分かっていません。
ocean
ベテラン
会議室デビュー日: 2003/07/06
投稿数: 65
投稿日時: 2003-07-06 16:11
unibonさん、こんにちは。

Java1.5で導入予定のGenericsで、きれいに実現できると思います。
コンパイルしてみた訳ではないので、間違っていたらごめんなさい。
継承関係は逆になっています。

コード:


/////////////////////////////
// 実装

public class Magnifier<BaseCanvas extends Canvas> extends BaseCanvas
{
public void paint(Graphics canvasGraphics)
{
Image image = createImage(100, 100);
Graphics imageGraphics = image.getGraphics();
super.paint(imageGraphics);
imageGraphics.dispose();
canvasGraphics.drawImage(image, 0, 0, 500, 500, this);
}
}

/////////////////////////////
// 使用

Manifier<MyCanvas> c = new Magnifier<MyCanvas>();





[ メッセージ編集済み 編集者: ocean 編集日時 2003-07-06 16:13 ]
Astmild
常連さん
会議室デビュー日: 2003/06/09
投稿数: 30
お住まい・勤務地: 大田区
投稿日時: 2003-07-06 22:28
unibonさん、こんにちは。

説明が言葉足らずでした。失礼しました。
”同じサブクラス内で処理”とは、
別のクラスのメソッド Print.printer() が PrintSub1.printer() を呼ぶのではなく、
同じクラスのメソッド PrintSub1.printer() が PrintSub1.printer() を呼んでいた、
と言ったつもりでした。

引用:
unibonさんの書き込み(2003-07-03 17:04)より:
あるクラスの paint メソッドから、そのサブクラスの foo メソッドを呼ぶことができます。

という言葉に引っ掛かり、JAVAってそんな動きしたっけかな?と思って実験しました。



引用:
unibonさんの書き込み(2003-07-06 12:50)より:
引用:
…略…
super#methodを固有処理に挟むのではなく、
super#methodに固有処理を挟みたい。
…略…

そうです。…略…


スーパークラスに固有処理を書いてしまったら汎化−特化関係が
成立しないのではないでしょうか。
共通部分を括るのが汎化です。sub.paint()は共通でない処理だから
サブクラスに書くのだと思います。共通処理が共通でない処理に依存
したら、”共通”ではなくなる気がしますが、どうでしょう?
かずくん
ぬし
会議室デビュー日: 2003/01/08
投稿数: 759
お住まい・勤務地: 太陽系第三惑星
投稿日時: 2003-07-07 11:52
田村です。こんにちわ

引用:

私は paint メソッドというメソッドは、サブクラスで挙動を再定義できるために
コールバックされるメソッドという位置付けであると理解しているのですが、
そのためのコールバックメソッドが paint と別に paint2/paintsub ができてしまい、
インターフェースが「汚く」なってしまうのを避けたいという思いがあります。



たとえ、インターフェースが増えても、再利用性、拡張性が向上するのであれば
1つのメソッド(継承関係にあるものも含む)で解決するのではなく、
複数のメソッドに分割する方法を選ぶというのが、私の考えです。

私の提示した方法は、継承ベースのパターンであるので、
クラス数の増大を避けることはできません。

この場合、描画に特化したインターフェースを作り、
paint() メソッド内でこのインターフェースのメソッドに処理を転送する方が
むしろ処理がすっきりするのではないかと思います。

呼び出し元のプロパティが必要な場合は、描画クラスに必要な値を渡したのでは
インターフェースが複雑化する場合、描画クラスに処理を委譲するのもありだと思います。
ocean
ベテラン
会議室デビュー日: 2003/07/06
投稿数: 65
投稿日時: 2003-07-07 19:12
引用:

かずくんさんの書き込み (2003-07-07 11:52) より:
この場合、描画に特化したインターフェースを作り、
paint() メソッド内でこのインターフェースのメソッドに処理を転送する方が
むしろ処理がすっきりするのではないかと思います。



同感です。ただ、既存の Canvas に拡大機能をつけたいときは、テンプレート+継承も使える方法だと思います。

/////////////////////////////////////////

sub.paint()みたいな書き方は、理論的には可能だと思います。(ただ、仮想関数の仕組みについて詳しいわけではないので、とんちんかんなことを言っているかもしれません。

コード:
class Base
{
	void hoge()
	{
		// ???
		sub.hoge();
		// ???
	}
}

class Derived extends Base
{
	watch_parent_on_vtable_creation void hoge()
	{
		// ???
	}
}




仮想関数のポインタをvtableに格納する際、通常と同じくまず、Derived##hoge() のポインタを格納しようとします。しかし、watch_parent_on_vtable_creation が定義されているので、代わりに Base##hoge() のポインタを格納します。sub.paint()は、sub が定義されていないときはコンパイルエラーにし、無限ループについては func1() { func2(); } func2() { func1(); } をコンパイルエラーにしないのと同様、エラーにはしません。

こうすると可能な気はしますが、仮想関数の流れがあちこちに飛ぶため、処理が読めなくなる危険性があります。また、sub.paint() がどの型の派生クラスの paint() を呼ぶかを、関数ポインタで記憶するか(必要なメモリが跳ね上がる気がする)、クラスの継承樹を実行時にたどる(必要な時間が跳ね上がる)必要があります。単に関数名で置換するのは、コードの肥大化を招くと思います。

引用:

私は paint メソッドというメソッドは、サブクラスで挙動を再定義できるために
コールバックされるメソッドという位置付けであると理解しているのですが、
そのためのコールバックメソッドが paint と別に paint2/paintsub ができてしまい、
インターフェースが「汚く」なってしまうのを避けたいという思いがあります。



同感ですが、同名仮想関数に解決方法を求めてしまうと、他の汚さを招くため、望ましくないのでしょう。

unibon
ぬし
会議室デビュー日: 2002/08/22
投稿数: 1532
お住まい・勤務地: 美人谷        良回答(20pt)
投稿日時: 2003-07-08 09:23
unibon です。こんにちわ。

引用:

oceanさんの書き込み (2003-07-06 16:11) より:
Java1.5で導入予定のGenericsで、きれいに実現できると思います。
コンパイルしてみた訳ではないので、間違っていたらごめんなさい。
継承関係は逆になっています。


ありがとうございます。Generics でできちゃうんですか。
Java の新しい機能はほとんど知らないので、
失礼ながらちょっと半信半疑なのですが(すみません)、
示されたサンプルと共に調べてみます。
ありがとうございます。

引用:

Astmildさんの書き込み (2003-07-06 22:28) より:
”同じサブクラス内で処理”とは、
別のクラスのメソッド Print.printer() が PrintSub1.printer() を呼ぶのではなく、
同じクラスのメソッド PrintSub1.printer() が PrintSub1.printer() を呼んでいた、
と言ったつもりでした。


すみません。Java の実行時の型はひとつだけですから、前者はありえないので、
ないものだとして除外してしまった前提で書いていました。

引用:

かずくんさんの書き込み (2003-07-07 11:52) より:
この場合、描画に特化したインターフェースを作り、
paint() メソッド内でこのインターフェースのメソッドに処理を転送する方が
むしろ処理がすっきりするのではないかと思います。


そうですよね。教えていただいて、考えもすっきりしました。
Component クラスは paint メソッドをオーバライドする形式になっていますが、
KeyListener などのように実行時にコールバックを追加できるようにすればいいんですよね。
すなわち、おっしゃるような「描画に特化したインターフェース」として
コード:
interface Painter {
    void paint(Graphics g);
}


があって、Component クラスは paint メソッドを持たず、代わりに、
public void addPainter(Painter painter)
などのようなインターフェースを備えておけば、
別段、継承関係を使わずともできますね。
これは結局は「委譲」のやりかたになると思います。

スキルアップ/キャリアアップ(JOB@IT)