javaに関数ポインタが無くて対抗した件に対する突っ込みに対するレス

ほんのちょっぴり(盛り上がった|大事になった)、1つ前のエントリー「Javaで関数ポインタチックなことがしたいよママン!」という話題。

お二方からコード付きの突っ込みをいただきまして、大変勉強させていただいた感じで大変感謝であります。

そもそも何が問題だったのか

おさらい…というか、後出しの条件になるかも。^^;



「条件を満たしたら*1、指定したクラスの指定した関数をコールバックして欲しい」



というのがそもそもでした。

コールバックして欲しい関数群は仮として次のような定義があったとします。 それぞれの関数を特定の条件によって呼び分けたいと。

// この子は他人のコードなので変更はできず
// ってか なんで interface にしたんだろう… class でえぇやん
interface FoodGiver
{
    int toSuigintou(Food food);
    int toKanaria(Food food);
    int toSuiseiseki(Food food);
    int toSouseiseki(Food food);
};

たとえば…ってか実際に困ったこと

メニュー項目から「金糸雀」を選んだら toKanaria を呼ぶみたいな実装が欲しいとします。


Eclipse (ってか、JFace?) に、メニューを管理する MenuManager と、メニューが選択された場合にコールバックするオブジェクトを指定する Action というクラスがあります。 利用用途としてはこんな感じ。(一部省略してるけど)

class RozenMaiden {

  FoodGiver m_pcGiver = どうにかする;

  private class GiveSuigintouAction
  {
    private Food f;
    public GiveSuigintouAction(Food f) {
      this.f = f;
    }

    @Override
    public void run() {
      m_pcGiver.toSuigntou(f);
    }
    
    @Override
    public String getText() {
      return "水銀燈 (&M)";
    }
  };

  private class GiveKanariaAction
  {
    private Food f;
    public GiveSuigintouAction(Food f) {
      this.f = f;
    }

    @Override
    void run() {
      m_pcGiver.toKanaria(f);
    }
    
    @Override
    public String getText() {
      return "金糸雀 (&K)";
    }
  };

  /* クラス定義略 */

  void func(Food f)
  {
    MenuManager m = new MenuManager();
    m.add(new GiveSuigintouAction(f));
    m.add(new GiveKanariaAction(f));
    m.add(new GiveSuiseisekiAction(f));
    m.add(new GiveSouseisekiAction(f));
    
    // m をごにょごにょしてメニューを表示する
  };
};

でまぁ、サンプルソースコードを書いてる時点で「クラス定義略」と書いてある通り クラス定義が面倒 なんっすよ…。 なんで別の関数呼ぶためだけに、クラス一つ作らなきゃならんのかと。 おかげでサンプルコードでもコード行数が長くなっとるちゅーねん…!

希望としては - JavaC# での仮想コード…ってか C# かも(ぉ

こういうコードが書ければ、クラス量も減るわコードも短くなるわ面倒じゃ無くなるわと、良い感じになると思うのですよ。 すっきりしていってね!

public class RozenMaiden
{
    delegate int TCallbackFunc(Food f);

    class MyCallbackAction extends Action
    {
        Food            m_pcFood;
        String          m_strText;
        TCallbackFunc   m_fn;
    
        public MyCallbackAction(Food f, String strText, TCallbackFunc fn) {
            m_pcFood  = f;
            m_strText = strText;
            m_fn      = fn;
        }
    
        @Override
        public void run() {
            preOperation();
            m_fn(m_pcFood);
        }
    
        @Override
        public String getText() {
            return m_strText;
        }

        priavte void preOperation() {
            // ごーかな処理
        }
    };

    FoodGiver m_pcGiver = どうにかこうにか;

    public void func(Food f)
    {
        MenuManager m = new MenuManager();
        m.add(new MyCallbackAction(f, "水銀燈 (&M)", new TCallbackFunc(m_pcGiver.toSuigintou)));
        m.add(new MyCallbackAction(f, "金糸雀 (&K)", new TCallbackFunc(m_pcGiver.toKanaria)));
        m.add(new MyCallbackAction(f, "翠星石 (&J)", new TCallbackFunc(m_pcGiver.toSuiseiseki)));
        m.add(new MyCallbackAction(f, "蒼星石 (&L)", new TCallbackFunc(m_pcGiver.toSouseiseki)));
    }
};


しかし、Java には delegate は存在しないので、おいらは「関数名のString」を渡すようにして、リフレクションを書けるように実装した…というワケでした。

メソッドを取得しているあたりで、少し「なんじゃこりゃ臭」を感じはしますが、クラスが繁殖するよりかは良いかなぁ…と思ってます。^^;

public class RozenMaiden
{
    class MyCallbackAction extends Action
    {
        Food    m_pcFood;
        String  m_strText;
        String  m_strFuncName;
    
        public MyCallbackAction(Food f, String strText, String strFuncName) {
            m_pcFood      = f;
            m_strText     = strText;
            m_strFuncName = strFuncName;
        }
    
        @Override
        public void run() {
            preOperation();
            try
            {
                Method m = FoodGiver.class.getMethod(strFuncName, Food.class);
                m.invoke(m_pcGiver, m_pcFood);
            }
            catch(Exception e){} // めんどい(ぉ
        }
    
        @Override
        public String getText() {
            return m_strText;
        }
        
        priavte void preOperation() {
            // 泣ける処理
        }
    };

    FoodGiver m_pcGiver = どうにかこうにか;

    public void func(Food f)
    {
        MenuManager m = new MenuManager();
        m.add(new MyCallbackAction(f, "水銀燈 (&M)", "toSuigintou"));
        m.add(new MyCallbackAction(f, "金糸雀 (&K)", "toKanaria"));
        m.add(new MyCallbackAction(f, "翠星石 (&J)", "toSuiseiseki"));
        m.add(new MyCallbackAction(f, "蒼星石 (&L)", "toSouseiseki"));
    }
};

たろうblogさんのコードをおいら的に見る

http://talototo.blog48.fc2.com/blog-entry-333.html

突っ込みをいただきました。ありがとうございます。m(_ _)m


そして大変恐縮なのですが(ってか、確かに何も書いてなかったのですが…)、今回の場合の Food は他人のコード*2故にいじくれない環境だったりします。^^;

そのため基底クラスに自分のためにメソッドを書くことも出来ないため、クラスを派生してごにょるにしても結局リフレクションが必要になってしまうと言う…(ぉ


しかし面白いコードだなぁと思いましたわ。というのも「Double Dispatch」なるものは初めて知ったクチだったりしまして…^^;

オーバーライド先に飛んだ後に this を渡す時には型情報が派生先のものになる故に、オーバーロードしている関数について自動的に最適なものが選ばれる…ってのは面白いなぁと

…と、簡単にぐぐってみたら、Visitor パターンと深い関係があると知り、ちょうど持っていた Java言語で学ぶデザインパターン と併用して、勉強させていただきました。(本題とはずれちゃってはいますけれども)

rayfill さんのコードを見る

http://d.hatena.ne.jp/rayfill/20080712#1215855717

うん、なんかすごい。

関数呼び出し自体を Hook できるとはおもわなんだ(ぉ

targetProxy.Invoke1();
targetProxy.Invoke2();

を実行した時、単純に "Target#Invoke1()", "Target#Invoke2()" が呼ばれるだけな気がしてみるものの、出力結果には after やら before やらついててなんじゃらほい…と最初は疑問だったのですが、どう考えても InterfaceProxy<T>#invoke() が呼ばれてごにょごにょしてるよねコレ という。

TargetInterface#Inovke1 を呼び出したはずなのに、InterfaceProxy<T>#invoke() が呼ばれた。な、何を言っているかわからねぇとおもうが(ry

知らないとそんな気分になりそう。(ぉ



>T.classとかかけない時点でもうね。new T();とかもできないし

個人的には instanceof T も無理でうっへりでした :-P

新規対策どうするのか

結局リフレクションのままかなぁ…と。^^;

たろうblogさんの方は、残念ながら現状の私のコードでは利用できませんし、rayfill さんの方は面白いとは思うのですがちょっと事が大事過ぎるかなぁーと(というか怒られそう:ぉ)


ただ、Visitor パターンについては早速今日勉強がてらに会社で利用してみたのですが、データと処理が見事に分離できて非常に良いコードが書けた感じでした。 良いパターンを学習できたと感じています。

最後に何が言いたいって

他人コードってやっぱり勉強になるなぁー(ぉ

おもしろいおもしろい



お二方、本当にありがとうございました!

…と、なんか閉めていますが、相変わらず突っ込みは募集してます。 是非とも Javaのポリシーをご教示いただければと…!(他力本願

*1:イベント発生とかなんかいろいろ

*2:というより、自動生成コード