Conception de code: délégation de fonctions arbitraires

9

Sur PPCG, nous avons fréquemment des défis King of the Hill , qui opposent différents robots de code les uns aux autres. Nous n'aimons pas limiter ces défis à un seul langage, nous effectuons donc des communications multiplateformes sur des E / S standard.

Mon objectif est d'écrire un cadre que les rédacteurs de défis pourront utiliser pour faciliter la rédaction de ces défis. J'ai trouvé les conditions suivantes que j'aimerais remplir:

  1. L'auteur du défi est capable de créer une classe où les méthodes représentent chacune des communications distinctes . Par exemple, sur notre défi Good vs Evil , l'écrivain ferait une Playerclasse qui a une abstract boolean vote(List<List<Boolean>> history)méthode dessus.

  2. Le contrôleur est en mesure de fournir des instances de la classe ci-dessus qui communiquent via des E / S standard lorsque les méthodes susmentionnées sont appelées . Cela dit, toutes les instances de la classe ci-dessus ne communiqueront pas nécessairement via les E / S standard. 3 des bots peuvent être des bots Java natifs (qui remplacent simplement la Playerclasse, où 2 autres sont dans une autre langue)

  3. Les méthodes n'auront pas toujours le même nombre d'arguments (elles n'auront pas toujours une valeur de retour)

  4. J'aimerais que l'auteur du défi doive faire le moins de travail possible pour travailler avec mon framework.

Je ne suis pas contre l'utilisation de la réflexion pour résoudre ces problèmes. J'ai envisagé de demander à l'auteur du défi de faire quelque chose comme:

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);
    }
}

mais s'il existe plusieurs méthodes, cela peut devenir assez répétitif, et le casting constant n'est pas amusant. ( sendMessagedans cet exemple, accepterait un nombre variable d' Objectarguments et renverrait un Object)

Y a-t-il une meilleure manière de faire cela?

Nathan Merrill
la source
1
Je suis confus au sujet de la PlayerComm extends Playerpartie " ". Tous les entrants Java s'étendent-ils Player, et cette PlayerCommclasse est un adaptateur pour les entrants non Java?
ZeroOne
Oui, c'est vrai
Nathan Merrill
Donc, par curiosité ... Avez-vous réussi à trouver une sorte de bonne solution pour cela?
ZeroOne
Nan. Je ne pense pas que ce que je veux soit possible en Java: /
Nathan Merrill

Réponses:

1

OK donc les choses ont dégénéré et je me suis retrouvé avec les dix classes suivantes ...

L'essentiel de cette méthode est que toute communication se fait en utilisant la Messageclasse, c'est-à-dire que le jeu n'appelle jamais directement les méthodes des joueurs, mais toujours en utilisant une classe communicative de votre framework. Il existe un communicateur basé sur la réflexion pour les classes Java natives, puis il doit y avoir un communicateur personnalisé pour tous les joueurs non Java. Message<Integer> message = new Message<>("say", Integer.class, "Hello");initialiserait un message dans une méthode nommée sayavec un paramètre "Hello"renvoyant un Integer. Celui-ci est ensuite transmis à un communicateur (généré à l'aide d'une usine basée sur le type de lecteur) qui exécute ensuite la commande.

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 D' autres mots - clés dans mon esprit que je ne peux pas tout à fait en rien affinez bien utile maintenant. Motif de commande , Motif visiteur , java.lang.reflect.ParameterizedType )

Zéro un
la source
Mon objectif est d'empêcher que la personne qui a fait Playerde l'écriture ne soit PlayerCommobligée. Alors que les interfaces de communication communiquent automatiquement avec moi, je rencontrerais toujours le même problème d'avoir à écrire la même sendRequest()fonction dans chaque méthode.
Nathan Merrill
J'ai réécrit ma réponse. Cependant, je me rends maintenant compte que l'utilisation du modèle de façade pourrait en fait être la voie à suivre ici, en enveloppant les entrées non Java dans ce qui ressemble exactement à une entrée Java. Donc pas de couchage avec des communicateurs ou de la réflexion.
ZeroOne
2
"OK donc les choses ont dégénéré et je me suis retrouvé avec les dix cours suivants" Si j'avais un nickel ...
Jack
Aïe, mes yeux! Quoi qu'il en soit, nous pourrions obtenir un diagramme de classe pour aller avec ces 10 classes? Ou êtes-vous trop occupé à écrire la réponse de votre motif de façade?
candied_orange
@CandiedOrange, en fait, je pense que j'ai déjà passé assez de temps avec cette question. J'espère que quelqu'un d'autre donnera sa version d'une solution, éventuellement en utilisant un motif de façade.
ZeroOne