- PR -

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

投稿者投稿内容
H2
ぬし
会議室デビュー日: 2001/09/06
投稿数: 586
お住まい・勤務地: 港
投稿日時: 2003-07-04 09:22
Unibonさんが言われるような親クラスから子クラスの同名のメソッドを呼ぶというのはできませんし、しない方がいいと思います。

class Super
class SubA extends Super
class SubB extends Super
というクラスがあるとします。superにはpaintというメソッドがあり、SubAもSubBもpaintをオーバーライドしています。

1.オブジェクトのTrue TypeがSuperの場合。(Super s = new Super()とした場合)
sからサブクラスのpaintを呼ぼうとした場合、プログラムはどのサブクラスを使えばいいのか分からない。SubAのpaint?それともSubBのpaint? また、どちらのクラスを使うか分かっている場合でも s からSubA か SubBへの一時的なダウンキャストをしないといけない。これは危険です。

2. オブジェクトのTrue TypeがSubAの場合。(Super s = new SubA()とした場合)
s の型はSuperではあるが、本当の型はSubAである。したがって、ダイナミックバインディングが行われ、SubAのpaintが呼び出される。この場合、Unibonさんが行おうとしていることは無意味です。なぜなら、明示的にしない限り、呼ばれるのはSubA.paintであり、Super.paintではないからです。もし、ここでSubA.paintでsuper.paint()があり、Super.paintで、sub.paint()があると皆さんが指摘しているような無限ループに落ち込みます。

3.依存関係の複雑化
UMLのクラスダイアグラムを見るとよく分かりますが、SubがSuperを継承するとき、継承を表す線の矢印はSubからSuperを指しています。これはSubがSuperに依存するという意味です。もし、上記の問題が解決され親クラスから子クラスのメソッドを呼べる場合、この矢印がSuperからSubへと指すことになります。つまり、クラス間の依存関係が増え、開発工程や管理が面倒になります。

4.解決法
*柔軟な解決法としてはやはりTemplate Methodがあります。
*もしくはSubからsuper.paint()を呼び出し、その前後に拡張機能をつけるべきです。
Wata
ぬし
会議室デビュー日: 2003/05/17
投稿数: 279
投稿日時: 2003-07-04 09:59
ども、Wataです。
これは「継承よりもコンポジションを選べ」の格言で上手くいくのでは?
つまり、
コード:
import java.awt.*;

class MyCanvas extends Canvas {

     private final Canvas sub;

     /** 拡大描画したいCanvasを引数にとるコンストラクタ */
     MyCanvas(Canvas sub){
          this.sub = sub;
     }

    public void paint(Graphics canvasGraphics) { // 5 倍に拡大して表示する機能を持つ paint メソッド。
        super.paint(canvasGraphics);
        Image image = createImage(100, 100);
        Graphics imageGraphics = image.getGraphics();
        sub.paint(imageGraphics); // subのpaintを呼び出す。(転送)
        imageGraphics.dispose();
        canvasGraphics.drawImage(image, 0, 0, 500, 500, this);
    }
}


みたいに。
#上手く動くかは確認してません。
maru
ぬし
会議室デビュー日: 2003/01/27
投稿数: 412
投稿日時: 2003-07-04 10:04
こんにちは。

↑ でも、やっぱり、コンパイルでとおっても呼び出しはサブクラスの paint から
なので、スーパークラスのpaintは結局呼ばれないのではないでしょうか?

H2さんの言われるように、そもそもサブクラスからsuperクラスのメソッドを呼び出して、
拡張機能はその呼び出しの前後につけるべきで、それで事足りるような気がしますが・・?

[ メッセージ編集済み 編集者: maru 編集日時 2003-07-04 10:06 ]
TO-R
ベテラン
会議室デビュー日: 2002/07/11
投稿数: 93
投稿日時: 2003-07-04 11:57
要するにコールバック・プロシージャが欲しいんですよね?

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

2、3試してみたのですが、その中で一番美しいのは…

コード:
//---Super.java---

import java.io.*;

public class Super {
public void method(PrintStream o) {
this.method(o, true);
}

public void method(PrintStream o, boolean flg) {
o.println("Super#method");
this.method(o, false);
}
}

//---Sub.java---
import java.io.*;

public class Sub extends Super {
public void method(PrintStream o, boolean flg) {
if (flg) {
super.method(o, true);
}
else {
o.println("Sub#method");
}
}
}


//---Test.java---
public class Test {
static void main(String[] args) {
Sub sub = new Sub();
sub.method(System.out);
}
}



クラスSuperにオブジェクトsuperを記述できない(継承したサブクラスで
暗黙的に利用することができない)ので、呼び分けの肝(キモ)の部分は、
クラスSub側に書かなきゃダメなんですよね(^^;


コールバックするメソッドを引き渡すような方法も考えられ
ますが、アプリケーション側からの呼び出しが煩雑になるかな?

  …あ、デフォルトのメソッドを呼べばいいから大丈夫かな?(^^;
  ご要望があれば、試しますが…。


いずれにしても、アプリケーション側から見た呼び出しインタフェースが
単純であれば、使う上ではOKでしょうから、内部的な複雑さには目を
つぶらなければならないでしょうね。


ではでは。


[ メッセージ編集済み 編集者: TO-R 編集日時 2003-07-04 12:16 ]
TO-R
ベテラン
会議室デビュー日: 2002/07/11
投稿数: 93
投稿日時: 2003-07-04 12:27
よくよく考えたら、オーバーロードしたのでは別メソッド作る方がキレイですね(^^;

…ということで、オーバーライドのみで実現する方法を…


コード:
//--- Super.java ---
import java.io.*;

public class Super {
  public void method(PrintStream o) {
    o.println("Super#method");
    this.method(o);
  }
}

//---Sub.java ---
import java.io.*;

public class Sub extends Super {
  private boolean _flag = false;
  public void method(PrintStream o) {
    if (_flag) {
      o.println("Sub#method");
      _flag = false;
    }
    else {
      _flag = true;
      super.method(o);
    }
  }
}

//---Test.java---
public class Test {
  static void main(String[] args) {
    Sub sub = new Sub();
    sub.method(System.out);
  }
}



やはり、呼び分け部分はSub側に来ます。

…あ、再入禁止にしておかないと不味いかな?(^^;
まぁ、参考までに…ということで、そこら辺は考慮してください。
---
Wata
ぬし
会議室デビュー日: 2003/05/17
投稿数: 279
投稿日時: 2003-07-04 13:19
ども、Wataです。

引用:

maruさんの書き込み (2003-07-04 10:04) より:
↑ でも、やっぱり、コンパイルでとおっても呼び出しはサブクラスの paint からなので、スーパークラスのpaintは結局呼ばれないのではないでしょうか?


これが私への書き込みだとして...
えっと、あまり意図がちゃんと伝わっていないようですね。
この場合、「継承よりもコンポジションを選べ」ですので、
描画したいオブジェクトはMyCanvasのサブクラスではありません。
MyCanvasはfinalでもかまいません。

別のCanvasクラスをインスタンス化して、それをMyCanvasにセットして、
MyCanvasをパネルなどに貼り付けます。よって、MyCanvasのpaintメソッドが呼び出されます。

以下に動作可能なコードを示します。
コード:

import java.awt.*;
import java.awt.event.*;
import java.text.SimpleDateFormat;
import java.util.Date;

public class CompsiteCanvasTest {

public static void main(String[] args) {

final Frame frame = new Frame("Test");
frame.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
frame.dispose();
}
});

frame.setLayout(new BorderLayout());

// 元になるキャンバスを作成する。
Canvas canvas = new Canvas() {
SimpleDateFormat format =
new SimpleDateFormat("yyyy/MM/dd HH:mm:ss.SSS");
public void paint(Graphics g) {
super.paint(g);
g.drawString(format.format(new Date()), 15, 15);
}
};
canvas.setSize(200, 30);
//canvas.setVisible(false); //表示しなくてもよい

// 2倍に表示するキャンバス
final ExpansionCanvas expansion = new ExpansionCavas(canvas, 2);
expansion.setSize(400, 60);

frame.add(expansion, BorderLayout.CENTER);
frame.add(canvas, BorderLayout.NORTH);

frame.pack();
frame.show();
}
}
/** 拡大表示キャンバス */
final class ExpansionCanvas extends Canvas {

private double ratio;
private Component target;
/** 拡大したいコンポーネントを引数に取る */
ExpansionCanvas(Component target, double ratio) {
super();
this.target = target;
this.ratio = ratio;
}
/** 拡大して表示する。 */
public void paint(Graphics canvasGraphics) {
Image image = createImage(target.getWidth(), target.getHeight());
Graphics imageGraphics = image.getGraphics();
target.paint(imageGraphics); // subのpaintを呼び出す。(転送)
imageGraphics.dispose();
canvasGraphics.drawImage(
image, 0, 0,
(int) (target.getWidth() * ratio),
(int) (target.getHeight() * ratio),
this);
}
}


より適切なクラス名、引数名に変更しました。
また、引数の型もCanvasからComponentに変更したのでより汎用的になってます。
欠点は拡大表示したい元のキャンバスもどこかに配置しなければいけないことですね...。

# しかし、拡大表示は例に過ぎず、あくまでサブクラスのメソッドが
# 呼びたいのだとすると、長いけど何の役にもたたないレスですね。
# まあ、いいたいことは、あまり継承にこだわるとよくないよってことです。

# 誤字訂正

[ メッセージ編集済み 編集者: Wata 編集日時 2003-07-04 13:22 ]
TO-R
ベテラン
会議室デビュー日: 2002/07/11
投稿数: 93
投稿日時: 2003-07-04 17:46
引用:

unibonさんの書き込み (2003-07-03 17:04) より:

疑問なのは、paint メソッドから別のメソッド(foo)は呼ぶことができるので、
サブクラスで foo メソッドをオーバライドすることで、
あるクラスの paint メソッドから、そのサブクラスの foo メソッドを呼ぶことができます。
これと同じようなことが同名のメソッドだとできない、
すなわち、あるクラスの paint メソッドからサブクラスの paint を呼べないのは、
なぜなのでしょうか。


お昼のカキコでは、争点がずれてしまっていました。すいません。

----------------
Super
----------------
----------------
+paint()
+paint2()
----------------

|extends
----------------
Sub
----------------
----------------
+paint2()
----------------

の場合に、Sub.paint()で呼ばれるのは、Super.paint()ではなく、
Subに継承されたpaint()なので、paint()からのpaint()の呼び出しは
サブクラスではなく、自クラスのメソッドを呼んでいます。

----------------
Super
----------------
----------------
+paint()
+paint2()
----------------

|extends
----------------
Sub
----------------
----------------
+paint() ←継承によりSuperと同じものが実装されたと見なされる。
+paint2()
----------------
よって、Super.paint()とSub.paint()は別物です。


具体的には…
コード:
//---Super.java---
public class Super {
  public void method() {
    System.out.println(this.getClass().getName());
  }
}

//---Sub.java---
public class Sub extends Super {
  public static void main(String[] args) {
    Super s = new Sub();
    s.method();
  }
}


…ということで。


疑問点には、これでお答えできているでしょうか?
H2
ぬし
会議室デビュー日: 2001/09/06
投稿数: 586
お住まい・勤務地: 港
投稿日時: 2003-07-05 01:48
引用:

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


そうですね。コンポジションの方が良いですね。忘れてました。 ケース・バイ・ケースなので Template Pattern、継承(superの前後)、もしくはコンポジッションどれでも、状況が許せば良いと思います。やっぱり密接的な関係がなければコンポジッションが良いと思いますが。

TO-Rさんの言われた方式を取るのであれば私はTemplate Patternを選びます・・・。コールバックはややこしいですし、後々の管理で別の担当者が理解に苦しみそうです。

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