Desain Kode: Delegasi fungsi sewenang-wenang


9

Di PPCG, kita sering menghadapi tantangan King of the Hill , yang mengadu bot kode yang berbeda satu sama lain. Kami tidak suka membatasi tantangan ini hanya dalam satu bahasa, jadi kami melakukan komunikasi lintas platform di atas standar I / O.

Tujuan saya adalah menulis kerangka kerja yang akan dapat digunakan oleh penulis tantangan untuk membuat menulis tantangan ini lebih mudah. Saya datang dengan persyaratan berikut yang ingin saya penuhi:

  1. Tantangan-penulis mampu membuat kelas di mana metode mewakili masing-masing komunikasi yang berbeda . Sebagai contoh, pada tantangan Baik vs Jahat kami , penulis akan membuat Playerkelas yang memiliki abstract boolean vote(List<List<Boolean>> history)metode di atasnya.

  2. Pengontrol mampu memberikan contoh kelas di atas yang berkomunikasi melalui standar I / O ketika metode yang disebut dipanggil . Yang mengatakan, tidak semua instance dari kelas di atas akan selalu berkomunikasi melalui standar I / O. 3 bot mungkin bot Java asli (yang hanya menimpa Playerkelas, di mana 2 bot lainnya dalam bahasa lain)

  3. Metode tidak akan selalu memiliki jumlah argumen yang sama (juga tidak akan selalu memiliki nilai balik)

  4. Saya ingin penulis tantangan harus melakukan pekerjaan sesedikit mungkin untuk bekerja dengan kerangka kerja saya.

Saya tidak menentang menggunakan refleksi untuk menyelesaikan masalah ini. Saya telah mempertimbangkan mengharuskan penulis tantangan untuk melakukan sesuatu seperti:

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

tetapi jika ada beberapa metode, ini bisa menjadi cukup berulang, dan casting konstan tidak menyenangkan. ( sendMessagedalam contoh ini akan menerima sejumlah variabel Objectargumen, dan mengembalikan Object)

Apakah ada cara yang lebih baik untuk melakukan ini?


1
Saya bingung tentang bagian " PlayerComm extends Player". Apakah semua pendaftar Java memperluas Player, dan PlayerCommkelas ini merupakan adaptor untuk pendatang non-Jawa?
ZeroOne

Ya, itu benar
Nathan Merrill

Jadi, karena penasaran ... Apakah Anda berhasil menemukan semacam solusi yang bagus untuk ini?
ZeroOne

Nggak. Saya tidak berpikir apa yang saya inginkan adalah mungkin di Jawa: /
Nathan Merrill

Jawaban:


1

OK jadi hal-hal semacam meningkat dan saya berakhir dengan sepuluh kelas berikut ...

Intinya dalam metode ini adalah bahwa semua komunikasi terjadi menggunakan Messagekelas, yaitu permainan tidak pernah memanggil metode pemain secara langsung tetapi selalu menggunakan kelas komunikator dari kerangka Anda. Ada komunikator berbasis refleksi untuk kelas Java asli dan kemudian harus ada komunikator khusus untuk semua pemain non-Jawa. Message<Integer> message = new Message<>("say", Integer.class, "Hello");akan menginisialisasi pesan ke metode bernama saydengan parameter "Hello"mengembalikan sebuah Integer. Ini kemudian diteruskan ke komunikator (dihasilkan menggunakan pabrik berdasarkan tipe pemain) yang kemudian mengeksekusi perintah.

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. Kata kunci lain dalam pikiran saya yang tidak dapat saya perbaiki menjadi sesuatu yang bermanfaat saat ini: Pola Perintah , Pola Pengunjung , java.lang.reflect.ParameterizedType )


Tujuan saya adalah untuk mencegah mewajibkan orang yang membuat Playertulisan PlayerCommsama sekali. Sementara antarmuka komunikator melakukan casting otomatis untuk saya, saya masih mengalami masalah yang sama karena harus menulis sendRequest()fungsi yang sama di setiap metode.
Nathan Merrill

Saya telah menulis ulang jawaban saya. Namun, saya sekarang menyadari bahwa menggunakan pola fasad mungkin sebenarnya cara untuk pergi ke sini, dengan membungkus entri non-Jawa ke apa yang tampak persis seperti entri Java. Jadi tidak main-main dengan beberapa komunikator atau refleksi.
ZeroOne

2
"Oke, jadi hal-hal semacam meningkat dan saya berakhir dengan sepuluh kelas berikut" Jika saya memiliki nikel ...
Jack

Ow, mataku! Lagi pula kita bisa mendapatkan diagram kelas untuk pergi dengan 10 kelas ini? Atau apakah Anda terlalu sibuk menulis jawaban pola fasad Anda?
candied_orange

@CandiedOrange, sebenarnya saya pikir saya sudah menghabiskan cukup banyak waktu dengan pertanyaan ini. Saya agak berharap orang lain akan memberikan versi solusi mereka, mungkin menggunakan pola fasad.
ZeroOne
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.