Code Design: Delega di funzioni arbitrarie

9

Su PPCG, abbiamo spesso King of the Hill sfide, che buca diversi code bot l'uno contro l'altro. Non ci piace limitare queste sfide a una singola lingua, quindi eseguiamo comunicazioni multipiattaforma rispetto agli I / O standard.

Il mio obiettivo è quello di scrivere un framework che gli autori delle sfide possano utilizzare per rendere più facile la scrittura di queste sfide. Ho trovato i seguenti requisiti che mi piacerebbe soddisfare:

  1. Il creatore di sfide è in grado di creare una classe dove i metodi rappresentano ciascuna delle distinte comunicazioni . Ad esempio, nella nostra Good vs Evil sfida, lo scrittore farebbe una classe Player che ha un metodo abstract boolean vote(List<List<Boolean>> history) su di esso.

  2. Il controller è in grado di fornire istanze della classe precedente che comunicano tramite I / O standard quando i metodi sopra menzionati sono chiamati . Detto questo, non tutte le istanze tutte della classe precedente comunicano necessariamente tramite I / O standard. 3 dei robot possono essere bot Java nativi (che sostituiscono semplicemente la classe Player , dove altri 2 sono in un'altra lingua)

  3. I metodi non avranno sempre lo stesso numero di argomenti (e non avranno sempre un valore di ritorno)

  4. Mi piacerebbe che lo scrittore di sfide dovesse fare il minor lavoro possibile per lavorare con il mio framework.

Non sono contrario all'uso del riflesso per risolvere questi problemi. Ho pensato di richiedere allo scrittore di sfide di fare qualcosa del tipo:

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

ma se ci sono diversi metodi, questo può diventare piuttosto ripetitivo, e il casting costante non è divertente. ( sendMessage in questo esempio accetta un numero variabile di argomenti Object e restituisce un Object )

C'è un modo migliore per farlo?

    
posta Nathan Merrill 24.03.2016 - 02:57
fonte

1 risposta

1

OK quindi le cose si sono intensificate e ho finito con le seguenti dieci classi ...

La linea di fondo in questo metodo è che tutte le comunicazioni avvengono usando la classe Message , cioè il gioco non chiama mai i metodi dei giocatori ma usa sempre una classe di comunicazione dal tuo framework. C'è un comunicatore basato sulla riflessione per le classi native Java e quindi deve esserci un comunicatore personalizzato per tutti i lettori non Java. Message<Integer> message = new Message<>("say", Integer.class, "Hello"); inizializza un messaggio in un metodo chiamato say con parametro "Hello" che restituisce un Integer . Questo viene quindi passato a un comunicatore (generato utilizzando una fabbrica in base al tipo di lettore) che esegue quindi il 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. Altre parole chiave nella mia mente che al momento non riesco a perfezionare nulla di utile: Pattern di comando , Pattern visitatore , java.lang.reflect.ParameterizedType )

    
risposta data 06.04.2016 - 01:04
fonte

Leggi altre domande sui tag