コード設計:任意の関数の委任


9

PPCGでは、King of the Hillの課題頻繁に発生します。これは、異なるコードボットを互いに対戦させるものです。これらの課題を単一の言語に限定するのは好きではないため、標準のI / Oを介してクロスプラットフォームの通信を行います。

私の目標は、チャレンジライターがこれらのチャレンジをより簡単に書くために使用できるフレームワークを書くことです。次の要件を満たしました。

  1. チャレンジライターは、メソッドが個別の通信のそれぞれを表すクラスを作成できます。たとえば、私たちのGood vs Evilチャレンジでは、ライターはメソッドを含むPlayerクラスを作成abstract boolean vote(List<List<Boolean>> history)します。

  2. コントローラは、前述のメソッドが呼び出されたときに標準I / Oを介し通信する上記のクラスのインスタンスを提供できます。ただし、上記のクラスのすべてのインスタンスが標準I / Oを介して通信する必要があるとは限りません。ボットのうち3つはネイティブJavaボットである可能性があります(Player別の2つが別の言語である場合、クラスをオーバーライドするだけです)。

  3. メソッドは常に同じ数の引数を持つわけではありません(また、常に戻り値を持つこともありません)。

  4. チャレンジライターが私のフレームワークで作業するためにできる限り少ない作業を行う必要があります。

私はこれらの問題を解決するために反射を使用することに反対していません。私はチャレンジライターに次のようなことを要求することを検討しました:

class PlayerComm extends Player {
    private Communicator communicator;
    public PlayerComm(Communicator communicator){
        this.communicator = communicator;
    }
    @Override
    boolean vote(List<List<Boolean>> history){
         return (Boolean)communicator.sendMessage(history);
    }
}

しかし、いくつかの方法がある場合、これはかなり繰り返しになる可能性があり、定数のキャストは楽しいものではありません。(sendMessageこの例では、可変数のObject引数を受け入れ、を返しますObject

これを行うより良い方法はありますか?


1
" PlayerComm extends Player"の部分について混乱しています。すべてのJavaエントリが拡張されていますか?PlayerこのPlayerCommクラスは非Javaエントリのアダプタですか?
ZeroOne 2016

はい、そうです
ネイサン・メリル

それで、好奇心から...このためのなんらかの素晴らしい解決策を考え出すことができましたか?
ZeroOne

いいえ。私はJavaで私が望むことは可能だとは思いません:/
Nathan Merrill

回答:


1

わかりましたので、何かがエスカレートし、私は次の10クラスになりました...

このメソッドの要点は、すべての通信がMessageクラスを使用して行われることです。つまり、ゲームはプレーヤーのメソッドを直接呼び出すことはなく、常にフレームワークのコミュニケータークラスを使用します。ネイティブJavaクラス用のリフレクションベースのコミュニケーターがあり、Java以外のすべてのプレーヤー用のカスタムコミュニケーターが必要です。Message<Integer> message = new Message<>("say", Integer.class, "Hello");は、を返すsayパラメータで指定されたメソッドへのメッセージを初期化します。次に、これはコミュニケーター(プレーヤーのタイプに基づくファクトリーを使用して生成)に渡され、コミュニケーターはコマンドを実行します。"Hello"Integer

import java.util.Optional;

public class Game {
    Player player; // In reality you'd have a list here

    public Game() {
        System.out.println("Game starts");
        player = new PlayerOne();
    }

    public void play() {
        Message<Boolean> message1 = new Message<>("x", Boolean.class, true, false, true);
        Message<Integer> message2 = new Message<>("y", Integer.class, "Hello");
        Result result1 = sendMessage(player, message1);
        System.out.println("Response 1: " + result1.getResult());
        Result result2 = sendMessage(player, message2);
        System.out.println("Response 2: " + result2.getResult());
    }

    private Result sendMessage(Player player, Message<?> message1) {
        return Optional.ofNullable(player)
                .map(Game::createCommunicator)
                .map(comm -> comm.executeCommand(message1))
                .get();
    }

    public static void main(String[] args) {
        Game game = new Game();
        game.play();
    }

    private static PlayerCommunicator createCommunicator(Player player) {
        if (player instanceof NativePlayer) {
            return new NativePlayerCommunicator((NativePlayer) player);
        }
        return new ExternalPlayerCommunicator((ExternalPlayer) player);
    }
}

public abstract class Player {}

public class ExternalPlayer extends Player {}

public abstract class NativePlayer extends Player {
    abstract boolean x(Boolean a, Boolean b, Boolean c);
    abstract Integer y(String yParam);
    abstract Void z(Void zParam);
}

public abstract class PlayerCommunicator {
    public abstract Result executeCommand(Message message);
}

import java.lang.reflect.Method;
public class NativePlayerCommunicator extends PlayerCommunicator {
    private NativePlayer player;
    public NativePlayerCommunicator(NativePlayer player) { this.player = player; }
    public Result executeCommand(Message message) {
        try {
            Method method = player.getClass().getDeclaredMethod(message.getMethod(), message.getParamTypes());
            return new Result(method.invoke(player, message.getArguments()));
        } catch (Exception e) { throw new RuntimeException(e); }
    }
}

public class ExternalPlayerCommunicator extends PlayerCommunicator {
    private ExternalPlayer player;
    public ExternalPlayerCommunicator(ExternalPlayer player) { this.player = player; }
    @Override
    public Result executeCommand(Message message) { /* Do some IO stuff */ return null; }
}

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class Message<OUT> {
    private final String method;
    private final Class<OUT> returnType;
    private final Object[] arguments;

    public Message(final String method, final Class<OUT> returnType, final Object... arguments) {
        this.method = method;
        this.returnType = returnType;
        this.arguments = arguments;
    }

    public String getMethod() { return method; }
    public Class<OUT> getReturnType() { return returnType; }
    public Object[] getArguments() { return arguments; }

    public Class[] getParamTypes() {
        List<Class> classes = Arrays.stream(arguments).map(Object::getClass).collect(Collectors.toList());
        Class[] classArray = Arrays.copyOf(classes.toArray(), classes.size(), Class[].class);
        return classArray;
    }
}

public class PlayerOne extends NativePlayer {
    @Override
    boolean x(Boolean a, Boolean b, Boolean c) {
        System.out.println(String.format("x called: %b %b %b", a, b, c));
        return a || b || c;
    }

    @Override
    Integer y(String yParam) {
        System.out.println("y called: " + yParam);
        return yParam.length();
    }

    @Override
    Void z(Void zParam) {
        System.out.println("z called");
        return null;
    }
}

public class Result {
    private final Object result;
    public Result(Object result) { this.result = result; }
    public Object getResult() { return result; }
}

(PS。今のところ、有用なものに完全に絞り込むことができないという私の心にある他のキーワード:Command PatternVisitor Patternjava.lang.reflect.ParameterizedType


私の目標は、作成Playerした人に書面PlayerCommを一切要求しないようにすることです。コミュニケーターインターフェイスは自動キャストを実行しsendRequest()ますが、各メソッドで同じ関数を記述する必要があるという同じ問題に遭遇します。
Nathan Merrill

回答を書き直しました。ただし、Java以外のエントリをJavaエントリのように見えるようにラップすることにより、ファサードパターンを使用することが実際にここに行く方法である可能性があることに気付きました。そのため、コミュニケーターや反射をいじり回す必要はありません。
ZeroOne 2016

2
「そうだね、何かがエスカレートして、次の10クラスになった」ニッケルがあったら…
ジャック

わぁ、目!とにかく、これらの10個のクラスに対応するクラス図を取得できますか?それとも、ファサードパターンの回答を書くのに忙しいですか?
candied_orange 2016年

@CandiedOrange、実際のところ、私はすでにこの質問に十分な時間を費やしていると思います。ファサードパターンを使用して、誰かが自分のバージョンのソリューションを提供してくれることを期待しています。
ZeroOne 2016年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.