코드 디자인 : 임의 함수 위임


9

PPCG에서 우리는 서로 다른 코드 봇을 움켜 쥐는 King of the Hill 과제를 자주 겪습니다 . 우리는 이러한 과제를 단일 언어로 제한하는 것을 좋아하지 않으므로 표준 I / O를 통해 플랫폼 간 통신을 수행합니다.

저의 목표는 도전 과제 작성자가 이러한 과제를보다 쉽게 ​​작성하는 데 사용할 수있는 프레임 워크를 작성하는 것입니다. 충족하려는 다음 요구 사항을 생각해 냈습니다.

  1. 도전 과제 작성자는 메소드가 각각의 고유 한 커뮤니케이션을 나타내는 클래스를 작성할 수 있습니다. 예를 들어, Good vs Evil 챌린지에서 작가는 메소드 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

네, 그렇습니다
Nathan Merrill

그래서 호기심에서 ... 당신은 이것에 대한 좋은 해결책을 생각해 냈습니까?
ZeroOne

아니. / : 나는 내가 원하는 것은 자바에서 가능하다고 생각하지 않습니다
나단 메릴

답변:


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 Pattern , Visitor Pattern , java.lang.reflect.ParameterizedType )으로 유용한 정보를 얻을 수 없습니다.


저의 목표는 작성한 사람이 Player글을 쓰지 않도록하는 PlayerComm것입니다. 커뮤니케이터 인터페이스가 자동 전송을 수행하지만 sendRequest()각 방법에서 동일한 기능 을 작성해야한다는 동일한 문제가 여전히 발생 합니다.
Nathan Merrill

나는 대답을 다시 썼다. 그러나 이제는 비 Java 항목을 Java 항목과 똑같은 모양으로 래핑 하여 외관 패턴을 사용하는 것이 실제로 여기에 갈 수있는 방법 이라는 것을 알고 있습니다. 따라서 일부 커뮤니케이터 또는 반성에 대해 장난하지 마십시오.
ZeroOne

2
"좋아요. 물건이 점점 커져서 다음 10 가지 수업으로 끝났습니다."니켈이 있다면 ...
Jack

아야, 내 눈! 어쨌든 우리는이 10 가지 클래스와 함께 갈 클래스 다이어그램을 얻을 수 있습니까? 아니면 정면 패턴 답변을 작성하는 데 너무 바쁘십니까?
candied_orange

@CandiedOrange, 사실 나는이 질문에 이미 충분한 시간을 보냈다고 생각합니다. 다른 사람이 외관 패턴을 사용하여 솔루션 버전을 제공하기를 바라고 있습니다.
ZeroOne
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.