Progettazione del codice: delega di funzioni arbitrarie


9

Su PPCG, abbiamo spesso sfide di King of the Hill , che mettono a confronto diversi bot di codice. Non ci piace limitare queste sfide a una sola lingua, quindi facciamo comunicazione multipiattaforma su I / O standard.

Il mio obiettivo è quello di scrivere un framework che gli autori delle sfide saranno in grado di utilizzare per facilitare la scrittura di queste sfide. Ho escogitato i seguenti requisiti che vorrei soddisfare:

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

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

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

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

Non sono contrario all'utilizzo della riflessione per risolvere questi problemi. Ho considerato di richiedere allo scrittore della sfida 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. ( sendMessagein questo esempio accetterebbe un numero variabile di Objectargomenti e restituirebbe un Object)

C'è un modo migliore per farlo?


1
Sono confuso per la parte " PlayerComm extends Player". Tutti i partecipanti Java si stanno estendendo Playere questa PlayerCommclasse è un adattatore per partecipanti non Java?
ZeroOne,

Sì, è vero
Nathan Merrill

Quindi, per curiosità ... Sei riuscito a trovare una sorta di bella soluzione per questo?
ZeroOne,

No. Non penso che ciò che voglio sia possibile in Java: /
Nathan Merrill il

Risposte:


1

OK, le cose si sono inasprite e ho finito con le seguenti dieci lezioni ...

La linea di fondo in questo metodo è che tutte le comunicazioni avvengono usando la Messageclasse, cioè il gioco non chiama mai direttamente i metodi dei giocatori ma usa sempre una classe comunicatore dal tuo framework. C'è un comunicatore basato sulla riflessione per le classi Java native e quindi ci deve essere un comunicatore personalizzato per tutti i giocatori non Java. Message<Integer> message = new Message<>("say", Integer.class, "Hello");inizializzare un messaggio su un metodo denominato saycon parametro che "Hello"restituisce un Integer. Questo viene quindi passato a un comunicatore (generato usando una factory in base al tipo di giocatore) che quindi esegue 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 non riesco proprio a perfezionare in qualcosa di utile in questo momento: Command Pattern , Visitor Pattern , java.lang.reflect.ParameterizedType )


Il mio obiettivo è quello di evitare di richiedere alla persona che ha Playerscritto di scrivere PlayerComm. Mentre le interfacce del comunicatore eseguono il casting automatico per me, mi imbatto ancora nello stesso problema di dover scrivere la stessa sendRequest()funzione in ciascun metodo.
Nathan Merrill,

Ho riscritto la mia risposta. Tuttavia, ora mi rendo conto che l'utilizzo del modello di facciata potrebbe effettivamente essere la strada da percorrere qui, avvolgendo le voci non Java in quello che sembra esattamente una voce Java. Quindi non scherzare con alcuni comunicatori o riflessioni.
ZeroOne,

2
"OK, le cose si sono intensificate e alla fine ho seguito le seguenti dieci lezioni" Se avessi un nickel ...
Jack,

Oh, i miei occhi! Ad ogni modo potremmo ottenere un diagramma di classe per andare con queste 10 classi? O sei troppo impegnato a scrivere la tua risposta al modello di facciata?
candied_orange,

@CandiedOrange, in effetti penso di aver già trascorso abbastanza tempo con questa domanda. Spero in qualche modo che qualcun altro dia la sua versione di una soluzione, possibilmente usando un modello di facciata.
ZeroOne,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.