Дизайн кода: делегирование произвольных функций

9

На PPCG у нас часто бывают вызовы King of the Hill , в которых разные боты кода сталкиваются друг с другом. Нам не нравится ограничивать эти проблемы одним языком, поэтому мы осуществляем кросс-платформенную связь через стандартный ввод-вывод.

Моя цель - написать фреймворк, который авторы задач смогут использовать для облегчения написания этих задач. Я выдвинул следующие требования, которые хотел бы выполнить:

  1. Автор запросов может создать класс, в котором методы представляют каждое отдельное сообщение . Например, на нашем задании «Добро против зла» писатель создает Playerкласс, в котором есть abstract boolean vote(List<List<Boolean>> history)метод.

  2. Контроллер может предоставлять экземпляры вышеуказанного класса, которые обмениваются данными через стандартный ввод-вывод при вызове вышеупомянутых методов . Тем не менее, не все экземпляры вышеуказанного класса обязательно будут обмениваться данными через стандартный ввод-вывод. 3 из ботов могут быть нативными Java-ботами (которые просто переопределяют Playerкласс, где еще 2 находятся на другом языке)

  3. Методы не всегда будут иметь одинаковое количество аргументов (и при этом они не всегда будут иметь возвращаемое значение)

  4. Мне бы хотелось, чтобы авторы задач выполняли как можно меньше работы, чтобы работать с моей структурой.

Я не против использования рефлексии для решения этих проблем. Я рассмотрел требование автора запроса сделать что-то вроде:

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

но если есть несколько методов, это может стать довольно повторяющимся, и постоянное приведение не весело. ( sendMessageв этом примере будет принимать переменное количество Objectаргументов и возвращать Object)

Есть лучший способ сделать это?

Натан Меррилл
источник
1
Я запутался насчет " PlayerComm extends Player" -части. Все ли участники Java расширяются Player, и этот PlayerCommкласс является адаптером для не-Java участников?
ZeroOne
Да, это правильно
Натан Меррилл
Итак, из любопытства ... Вам удалось найти какое-то хорошее решение для этого?
ZeroOne
Нет. Я не думаю, что то, что я хочу, возможно в Java: /
Натан Меррилл

Ответы:

1

ОК, все обострилось, и я закончил следующие десять уроков ...

Суть этого метода в том, что все общение происходит с использованием Messageкласса, то есть игра никогда не вызывает методы игроков напрямую, а всегда использует класс коммуникатора из вашей среды. Для родных классов Java есть коммуникатор, основанный на отражениях, а затем должен быть специальный коммуникатор для всех не-Java игроков. Message<Integer> message = new Message<>("say", Integer.class, "Hello");инициализирует сообщение для метода с именем, возвращающим sayпараметр . Затем он передается на коммуникатор (сгенерированный с использованием фабрики на основе типа проигрывателя), который затем выполняет команду."Hello"Integer

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 Другие ключевые слова в моей голове , что я не могу достаточно результаты во что - нибудь полезное прямо сейчас:. Команда , посетитель шаблон , java.lang.reflect.ParameterizedType )

ZeroOne
источник
Моя цель состоит в том, чтобы предотвратить требование человека, который сделал Playerот письма PlayerCommвообще. В то время как интерфейсы коммуникатора выполняют для меня автоматическое приведение, я все равно столкнулся с той же проблемой, связанной с необходимостью писать одну и ту же sendRequest()функцию для каждого метода.
Натан Меррилл
Я переписал свой ответ. Тем не менее, теперь я понимаю, что использование шаблона фасада на самом деле может быть подходящим способом, заключая не-Java-записи в то, что выглядит точно так же, как Java-запись. Так что не стоит дурачиться с некоторыми коммуникаторами или размышлениями.
ZeroOne
2
«Хорошо, все обострилось, и я закончил следующие десять уроков» Если бы у меня был никель ...
Джек,
Ой, мои глаза! В любом случае, мы могли бы получить диаграмму классов для этих 10 классов? Или вы слишком заняты написанием своего ответа по образцу фасада?
candied_orange
@CandiedOrange, на самом деле, я думаю, что я уже провел достаточно времени с этим вопросом. Я надеюсь, что кто-то другой даст свою версию решения, возможно, с использованием шаблона фасада.
ZeroOne