Design de código: delegação de funções arbitrárias

9

No PPCG, frequentemente enfrentamos desafios de King of the Hill , que colocam diferentes robôs de código uns contra os outros. Não gostamos de limitar esses desafios a um único idioma; portanto, fazemos comunicação entre plataformas através de E / S padrão.

Meu objetivo é escrever uma estrutura que os escritores de desafios possam usar para facilitar a escrita desses desafios. Eu vim com os seguintes requisitos que gostaria de cumprir:

  1. O escritor do desafio é capaz de criar uma classe em que os métodos representam cada uma das comunicações distintas . Por exemplo, em nosso desafio Bem contra o mal , o escritor faria uma Playerclasse que contenha um abstract boolean vote(List<List<Boolean>> history)método.

  2. O controlador é capaz de fornecer instâncias da classe acima que se comunicam via E / S padrão quando os métodos mencionados acima são chamados . Dito isto, nem todas as instâncias da classe acima se comunicarão necessariamente por E / S padrão. 3 dos bots podem ser Java nativos (que simplesmente substituem a Playerclasse, onde outros 2 estão em outro idioma)

  3. Os métodos nem sempre terão o mesmo número de argumentos (nem sempre terão um valor de retorno)

  4. Eu gostaria que o escritor de desafios tivesse que fazer o mínimo possível para trabalhar com minha estrutura.

Não sou contra o uso da reflexão para resolver esses problemas. Eu considerei exigir que o escritor do desafio fizesse algo como:

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

mas se houver vários métodos, isso pode ficar bastante repetitivo e a transmissão constante não é divertida. ( sendMessageneste exemplo, aceitaria um número variável de Objectargumentos e retornaria um Object)

Existe uma maneira melhor de fazer isso?

Nathan Merrill
fonte
11
Estou confuso sobre a PlayerComm extends Playerparte " ". Todos os participantes do Java estão se estendendo Playere esta PlayerCommclasse é um adaptador para os participantes não Java?
ZeroOne
Sim, isso mesmo #
Nathan Merrill
Então, por curiosidade ... Você conseguiu algum tipo de solução legal para isso?
ZeroOne
Não. Eu não acho que o que eu quero é possível em Java: /
Nathan Merrill

Respostas:

1

OK, então as coisas meio que aumentaram e eu acabei com as dez aulas seguintes ...

A conclusão deste método é que toda comunicação ocorre usando a Messageclasse, ou seja, o jogo nunca chama os métodos dos jogadores diretamente, mas sempre usando uma classe comunicadora da sua estrutura. Há um comunicador baseado em reflexão para classes Java nativas e, em seguida, deve haver um comunicador personalizado para todos os players que não são Java. Message<Integer> message = new Message<>("say", Integer.class, "Hello");inicializaria uma mensagem para um método nomeado saycom o parâmetro "Hello"retornando um Integer. Isso é passado para um comunicador (gerado usando uma fábrica com base no tipo de jogador) que, em seguida, executa o comando.

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. Outras palavras-chave em minha mente que não consigo refinar em nada útil no momento: padrão de comando , padrão de visitante , java.lang.reflect.ParameterizedType )

Zero um
fonte
Meu objetivo é evitar exigir que a pessoa que Playerescreveu tenha escrito PlayerComm. Enquanto as interfaces do comunicador fazem a transmissão automática para mim, eu continuaria com o mesmo problema de ter que escrever a mesma sendRequest()função em cada método.
Nathan Merrill
Eu reescrevi minha resposta. No entanto, agora percebo que o uso do padrão de fachada pode ser o caminho a seguir, envolvendo entradas não Java no que se parece exatamente com uma entrada Java. Portanto, não brinque com alguns comunicadores ou reflexões.
fácil
2
"OK assim que as coisas tipo de escalada e acabei com as seguintes classes dez" Se eu tivesse um níquel ...
Jack
Ai, meus olhos! De qualquer forma, podemos obter um diagrama de classes para acompanhar essas 10 classes? Ou você está muito ocupado escrevendo sua resposta de padrão de fachada?
Candied_orange 25/05
@ CandiedOrange, na verdade, acho que já passei tempo suficiente com essa pergunta. Espero que alguém dê a versão de uma solução, possivelmente usando um padrão de fachada.
ZeroOne 26/05