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


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.