【Java】呼び出し先を変更せずに、振る舞いを引数で渡す方法


多重ループや連携先での動作を切り替えたいとき
呼び出し側でコントロールして、呼び出し先はいじりたくない時ありますよね。

if文の嵐で見ずらくなったり、forループの性能が気になるときなど特にそうです。

大抵は、あきらめて、呼び出し先もいじって
カオスなソースになることがあると思いますが、
ちょっと呼び出し先を変えないで、振る舞いを変える方法を考えてみました。

JavaでLispのように関数を引数として渡す方法です。

たぶん
AdapterやProxy, hookなどのデザインパターンのような内容です。

C#版はこちら

考え方

要望

・いちいちclass全体を作り変えるのではなく、振る舞い部分だけのコーディングにしたい。
・呼び出し元で振る舞いをコントロールしたい。
・呼び出し先はソースを変えたくない。

このように、for loopの中に 数行だけ違う処理を入れたい時、 forループまるごとコピペするのは
アートなプログラマー?にはもだえ苦しむぐらい、我慢なりません。

実装方法

Javaでも ラムダ記法がとりいれられたようですが、
今回は無名のインナークラスの考え方と interfaceの考え方を使って、振る舞いを引数として渡します。

要するに、 interface で引数の型を定義して、
呼び出す前に、振る舞いをinterfaceに合わせて定義し渡します。
振る舞いの定義はよく Listenerで使われるようなやり方(違うやり方でもよいですが)で、さらっと定義します。

ソース

ソース構成

呼び出し元: caller()
呼び出し先:mainloop()

振る舞い定義 (interface部分)  CheckString()  ・・まあ言うても、難しく考えず、ここは引数と戻り値の定義だけだけ。引数を受けて、編集した値や文字列を返したり、別の関数を読んだり、条件のチェックをしたりと頭の中で考えておきます。

今回の例では、元の caller ()上で   CheckString()に合わせた振る舞いを具体的に定義し
mainloop()に 振る舞いを引数として渡します。

呼び出し先のmainloop()は全く変えずに、動作を変えることができるようになります。
(サンプルソースでは、2パターンの振る舞いで呼び出しをしている。
また、振る舞いは単純に文字列を見ているだけ)

お試し

コピペしてWeb上で直接動作確認してみてください。

ソース

public class HelloWorld{

    public static void main(String []args){
        HelloWorld app = new HelloWorld();
        app.caller();
    }
     
    void caller(){
             System.out.println("right phase"); 
             CheckString cs = new CheckString() {
                 public boolean check(String data){
                     return (data.equals("right"));
                 }
             };
             mainloop(cs);

             System.out.println("wrong phase"); 
             cs = new CheckString() {
                 public boolean check(String data){
                     return (data.equals("wrong"));
                 }
             };
             mainloop(cs);
     }
     
     void mainloop(CheckString cs){
         String s = "right";
         for (int i = 0 ; i < 2 ; i++){
             for (int j = 0 ; j < 2 ; j++){
                 if (cs.check(s))
                     System.out.println("Yes!!" + i + " " + j);
             }
         }
     }

}

interface CheckString{
    boolean check(String data);
}

補足:

類似

・オーバーライド
・メソッドインタフェース / ラムダ関数

サブクラスの同名メソッドで振る舞いを変えるのはオーバーライドで、メタフォーリズム(多態)と言います。ちなみに、同じクラス内または、継承クラス間との中において、引数で同名メソッドの動作を変えるのはオーバーロード。

classをきっちり定義しないで振る舞いを変える方法で
メソッドを引数として、渡す方法は 以下のような方法があります。
Java関数型インターフェース

実例

ソースの実例の構成は3セクションに分かれています。
呼び出し元:2パターン。呼び出し先の一部の振る舞いをこちら側でコントロールしたい
共通呼び出し先:振る舞いを変えたいが,loopの中でif分で切り替えたくない
・(振る舞い変える用のサブルーチン:直接IntPredicateの実装を指定して、なくしてもいい)

ここでは booleanを返し、intを引数にするタイプの定義IntPredicator を使います。
その行に注目!

    //呼出し元で、判断基準だけ変えたい
    //IntPredicateで切り替えている
     public static void removeNormalType(List<SuchGroup> suchGroupList){
        IntPredicate typeJudge = type -> isNormalType(type);
        removeSuchType(suchGroupList, typeJudge);
    }
    public static void removeIrregularType(List<SuchGroup> suchGroupList){
        IntPredicate typeJudge = type -> isIrregularType(type);
        removeSuchType(suchGroupList,typeJudge);
    }
     
    //呼び出し先で振る舞いを変える部分
    //IntPredicate に注目
    private static void removeSuchType(List<SuchGroup> suchGroupList, IntPredicate typeJudge){
        Iterator itrt = suchGroupList.iterator();
        SuchGroup sg;
        int t;
        for (;itrt.hasNext();){
            sg = (SuchGroup)itrt.next();
            t = sg.getType();
            if (typeJudge.test(t) ){
                itrt.remove();
            }
        }
    }
    //boolean型判定条件サポート用サブルーチン
    public static boolean isNormalType(int type){
        return (type == NORMAL1|| type == NORMAL2);
    }
    public static boolean isIrregularType(int type){
        return (type == IRREGULAR);
    }

IntPredicatorは  int引数+ Predicator(判断:つまりbooleanで返す) タイプの
メタメソッド定義方法です。 引数がintで明示されているので、if文のところで静的コンパイラがちぇっくできるようになりますね

ユーザが定義しなくても、標準で準備されている関数の一つです。
=のうしろの  t -> は  1つ目の引数名を tとして メソッド内容が
以後に定義されています((t){ ... }のようなもの)。

引数のタイプ、数、返り値を変えるときは 別のクラスが用意されています。
T/U型もあるので、ある程度自由なclassに対して処理できます。

参照: Java関数型インターフェースメモ(Hishidama's Java8 Functional Interface Memo) http://www.ne.jp/asahi/hishidama/home/tech/java/functionalinterface.html

引数なしで定義したい場合は () ->のように明示し、
複数の命令文がある場合、普通のメソッドのように{ }でくくります。

終わりに

こういうことができると、ソースの見通しの良さを維持したまま、
自由に動作を変えられることができますし
Lispのような生産性の高いコーディングもさくっと可能になります。

また AIや IoTなど を作るときにも、
Lispのようなやり方ができれば、柔軟性も高く応用が利くと思います。

Delicious にシェア
Digg にシェア
reddit にシェア
LinkedIn にシェア
LINEで送る
email this
Pocket

661 views.



コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です