代码设计:委托任意函数


9

在PPCG上,我们经常遇到“山丘之王”挑战,这使不同的代码bot相互竞争。我们不希望将这些挑战限制为单一语言,因此我们通过标准I / O进行跨平台通信。

我的目标是编写一个框架,使挑战编写者可以使用它来简化编写这些挑战的过程。我想满足以下要求:

  1. 挑战编写者可以创建一个类,其中方法表示每种不同的通信。例如,在我们的《善与恶》挑战中,作者将创建一个Player具有abstract boolean vote(List<List<Boolean>> history)方法的类。

  2. 当调用上述方法时,控制器能够提供通过标准I / O进行通信的上述类的实例。也就是说,并非上述类的所有实例都必须通过标准I / O进行通信。其中的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 2016年

是的,没错
Nathan Merrill

因此,出于好奇...您是否为此提出了一些不错的解决方案?
ZeroOne 2013年

不。我认为Java不可能实现我想要的东西:/
Nathan Merrill

Answers:


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。在我看来,我现在还不能完全将其提炼为其他有用的关键字:Command PatternVisitor Patternjava.lang.reflect.ParameterizedType


我的目标是完全避免要求Player写作的人PlayerComm。尽管通讯接口为我执行了自动强制转换,但我仍然遇到必须sendRequest()在每种方法中编写相同功能的相同问题。
内森·美林

我已经重写了答案。但是,我现在意识到,通过将非Java条目包装到看上去完全像Java条目的外观中,使用外观模式实际上可能是解决问题的方法。因此,不要与某些传播者或反思者鬼混。
ZeroOne

2
“好吧,事情就这样升级了,我结束了以下十节课。”如果我有镍...
杰克

哦,我的眼睛!无论如何,我们可以得到一个与这10个类一起使用的类图?还是您太忙于编写外观模式答案?
candied_orange

@CandiedOrange,事实上,我认为我已经花了足够的时间解决这个问题。我有点希望其他人可以使用立面模式给出他们解决方案的版本。
ZeroOne
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.