Projektowanie kodu: Delegowanie dowolnych funkcji

9

Na PPCG często mamy wyzwania King of the Hill , które rzucają przeciwko sobie różne boty kodu. Nie lubimy ograniczać tych wyzwań do jednego języka, dlatego prowadzimy komunikację między platformami w stosunku do standardowych operacji we / wy.

Moim celem jest napisanie frameworka, w którym autorzy wyzwań będą mogli ułatwić pisanie tych wyzwań. Wymyśliłem następujące wymagania, które chciałbym spełnić:

  1. Autor wyzwania jest w stanie stworzyć klasę, w której metody reprezentują każdą z odrębnych komunikatów . Na przykład w naszym wyzwaniu Dobry kontra Zły pisarz stworzyłby Playerklasę, która ma abstract boolean vote(List<List<Boolean>> history)metodę.

  2. Sterownik jest w stanie zapewnić instancje powyższej klasy, które komunikują się za pośrednictwem standardowych We / Wy, gdy wywoływane są wyżej wymienione metody . To powiedziawszy, nie wszystkie instancje powyższej klasy koniecznie będą komunikować się przez standardowe I / O. 3 boty mogą być natywnymi botami Java (które po prostu zastępują Playerklasę, gdzie kolejne 2 są w innym języku)

  3. Metody nie zawsze będą miały taką samą liczbę argumentów (nie zawsze będą miały wartość zwracaną)

  4. Chciałbym, aby twórca wyzwań musiał wykonać jak najmniej pracy, aby pracować z moim frameworkiem.

Nie jestem przeciwny używaniu refleksji do rozwiązania tych problemów. Zastanawiałem się, czy nie wymagać od autora zadania zrobienia czegoś takiego:

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

ale jeśli istnieje kilka metod, może to być dość powtarzalne, a ciągłe rzucanie nie jest zabawne. ( sendMessagew tym przykładzie zaakceptuje zmienną liczbę Objectargumentów i zwróci an Object)

Czy jest na to lepszy sposób?

Nathan Merrill
źródło
1
Jestem zdezorientowany co do części „ PlayerComm extends Player”. Czy wszystkie komponenty Java są rozszerzone Player, a ta PlayerCommklasa jest adapterem dla uczestników innych niż Java?
ZeroOne,
Tak, zgadza się
Nathan Merrill,
Więc z ciekawości ... Czy udało ci się znaleźć jakieś fajne rozwiązanie?
ZeroOne,
Nie. Nie sądzę, że to, czego chcę, jest możliwe w Javie: /
Nathan Merrill

Odpowiedzi:

1

OK, więc sprawy się eskalowały i skończyło się na następnych dziesięciu klasach

Najważniejsze w tej metodzie jest to, że cała komunikacja odbywa się przy użyciu Messageklasy, tj. Gra nigdy nie wywołuje metod graczy bezpośrednio, ale zawsze używa klasy komunikatora z twojego frameworka. Istnieje komunikator oparty na odbiciu dla rodzimych klas Java, a następnie musi istnieć niestandardowy komunikator dla wszystkich odtwarzaczy innych niż Java. Message<Integer> message = new Message<>("say", Integer.class, "Hello");zainicjuje komunikat do metody o nazwie sayz parametrem "Hello"zwracającym Integer. Jest on następnie przekazywany do komunikatora (generowanego przy użyciu fabryki opartej na typie odtwarzacza), który następnie wykonuje polecenie.

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 Inne słowa kluczowe w moim umyśle, że nie mogę całkiem zawężają się coś użytecznego prawej teraz. Wzór poleceń , odwiedzający , java.lang.reflect.ParameterizedType )

Zero jeden
źródło
Moim celem jest uniknięcie wymagania od osoby, która Playerpisała PlayerCommw ogóle. Podczas gdy interfejsy komunikatora wykonują dla mnie automatyczne rzutowanie, nadal mam ten sam problem z koniecznością napisania tej samej sendRequest()funkcji w każdej metodzie.
Nathan Merrill,
Przepisałem swoją odpowiedź. Teraz jednak zdaję sobie sprawę, że użycie wzorca elewacji może być dobrym sposobem na przejście tutaj, poprzez zawijanie wpisów spoza Javy w coś, co wygląda dokładnie jak wpis Java. Więc nie wygłupiać się za pomocą niektórych komunikatorów lub refleksji.
ZeroOne,
2
„OK, więc sprawy się eskalowały i skończyłem z następującymi dziesięcioma klasami” Gdybym miał nikiel ...
Jack
Ow, moje oczy! W każdym razie moglibyśmy uzyskać schemat klas, który będzie pasował do tych 10 klas? A może jesteś zbyt zajęty pisaniem odpowiedzi na wzór fasady?
candied_orange
@CandiedOrange, wydaje mi się, że spędziłem już wystarczająco dużo czasu z tym pytaniem. Mam nadzieję, że ktoś inny poda swoją wersję rozwiązania, prawdopodobnie wykorzystując wzór fasady.
ZeroOne