Kod Tasarımı: Keyfi işlevlerin devri


9

PPCG'de sık sık farklı kod botlarını birbirine düşüren King of the Hill zorluklarımız var. Bu zorlukları tek bir dille sınırlamaktan hoşlanmıyoruz, bu nedenle standart I / O üzerinden platformlar arası iletişim yapıyoruz.

Amacım, meydan okuma yazarlarının bu zorlukları yazmayı kolaylaştırmak için kullanabilecekleri bir çerçeve yazmak. Aşağıdaki şartları yerine getirmek istiyorum:

  1. Meydan okuma yazarı, yöntemlerin farklı iletişimlerin her birini temsil ettiği bir sınıf oluşturabilir . Örneğin, İyi ve Kötülük mücadelemizde yazar, üzerinde yöntem Playerolan bir sınıf abstract boolean vote(List<List<Boolean>> history)yapar.

  2. Kontrolör, yukarıda bahsedilen yöntemler çağrıldığında standart I / O üzerinden iletişim kuran yukarıdaki sınıfın örneklerini sağlayabilir . Bununla birlikte, yukarıdaki sınıfın tüm örnekleri standart G / Ç üzerinden iletişim kurmayabilir. Botlardan 3 tanesi yerel Java botları olabilir (bu sadece Player2'yi başka bir dilde olan sınıfı geçersiz kılar )

  3. Yöntemler her zaman aynı sayıda bağımsız değişkene sahip olmayacak (veya her zaman bir dönüş değerine sahip olmayacak)

  4. Meydan okuyan yazarın çerçevemle çalışmak için olabildiğince az iş yapmasını istiyorum.

Bu sorunları çözmek için yansıma kullanmaya karşı değilim. Meydan okuma yazarı gibi bir şey yapmak gerektiren düşündüm:

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

ancak birkaç yöntem varsa, bu oldukça tekrarlanabilir ve sürekli döküm eğlenceli değildir. ( sendMessagebu örnekte değişken sayıda Objectbağımsız değişkeni kabul eder ve döndürür Object)

Bunu yapmanın daha iyi bir yolu var mı?


1
" PlayerComm extends Player" Bölümü hakkında kafam karıştı . Tüm Java girişleri genişliyor mu Playerve bu PlayerCommsınıf Java dışı girişler için bir adaptör mü?
ZeroOne

Evet, bu doğru
Nathan Merrill

Peki, meraktan ... Bunun için güzel bir çözüm bulmayı başardınız mı?
ZeroOne

Hayır! Java'da istediğimin mümkün olduğunu düşünmüyorum: /
Nathan Merrill

Yanıtlar:


1

Tamam, bu yüzden şeyler biraz tırmandı ve ben aşağıdaki on ders ile sona erdi ...

Bu yöntemdeki sonuç, tüm iletişimin Messagesınıfı kullanarak gerçekleşmesidir , yani oyun asla oyuncuların yöntemlerini doğrudan çağırmaz, ancak her zaman çerçevenizden bir iletişimci sınıfı kullanır. Yerel Java sınıfları için yansıma tabanlı bir iletişimci vardır ve sonra tüm Java dışı oyuncular için özel bir iletişimci olmalıdır. parametresi döndüren bir Message<Integer> message = new Message<>("say", Integer.class, "Hello");yöntemle ileti başlatır . Bu daha sonra komutu yürüten bir iletişim cihazına (oynatıcı tipine dayalı bir fabrika kullanılarak üretilir) iletilir.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. Aklımdaki şu anda yararlı bir şey haline getiremediğim diğer anahtar kelimeler: Komut Deseni , Ziyaretçi Deseni , java.lang.reflect.ParameterizedType )


Amacım, hiç kimsenin Playeryazmasını istememek PlayerComm. Communicator arabirimleri benim için otomatik döküm yaparken, sendRequest()her yöntemde aynı işlevi yazmak zorunda kalmaya devam ediyorum .
Nathan Merrill

Cevabımı yeniden yazdım. Ancak, şimdi , Java dışı girişleri tam olarak bir Java girişi gibi görünen şeylere sararak cephe desenini kullanmanın aslında bu yolun olabileceğini anlıyorum . Yani hiçbir iletişimci veya yansıma ile dalga geçmeyi.
ZeroOne

2
"Tamam, bu yüzden şeyler biraz yükseldi ve ben aşağıdaki on sınıf ile sona erdi" Eğer bir nikel olsaydı ...
Jack

Ow, gözlerim! Her neyse, bu 10 sınıfla gitmek için bir sınıf diyagramı alabilir miyiz? Yoksa cephe desen cevabınızı yazmakla çok meşgul müsünüz?
candied_orange

@CandiedOrange, aslında bu soruyla zaten yeterince zaman harcadığımı düşünüyorum. Bir başkasının muhtemelen bir cephe deseni kullanarak bir çözüm versiyonunu vermesini umuyorum.
ZeroOne
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.