Дизайн коду: Делегування довільних функцій


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: /
Nathan Merrill

Відповіді:


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 )


Моя мета - не допустити того, щоб вимагати від людини, яка Playerписала, PlayerCommвзагалі писати. Хоча інтерфейси комунікатора роблять автоматичне кастинг для мене, я все-таки зіткнувся з тією ж проблемою, що потрібно писати одну й ту ж sendRequest()функцію в кожному методі.
Натан Меррілл

Я переписав свою відповідь. Однак тепер я розумію, що використання фасадного шаблону насправді може бути способом перейти сюди, вкладаючи записи, які не є Java, у те, що схоже на запис Java. Тож не дуріть з якимись комунікаторами чи роздумами.
ZeroOne

2
"Гаразд, все наросло, і я закінчив наступні десять класів" Якби у мене був нікель ...
Джек

Ой, мої очі! У будь-якому випадку ми могли отримати схему класів для цих 10 класів? Або ви занадто зайняті написанням відповіді на свій фасад?
candied_orange

@CandiedOrange, по суті, я думаю, що я вже витратив достатньо часу на це питання. Я сподіваюся, що хтось інший дасть свою версію рішення, можливо, використовуючи фасадний малюнок.
ZeroOne
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.