การออกแบบรหัส: การมอบหมายหน้าที่ตามอำเภอใจ


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

ใช่ถูกต้องแล้ว
นาธานเมอร์ริลล์

ดังนั้นจากความอยากรู้ ... คุณจัดการกับวิธีแก้ปัญหาที่ดีสำหรับเรื่องนี้หรือไม่?
ZeroOne

Nope ฉันไม่คิดว่าสิ่งที่ฉันต้องการเป็นไปได้ใน Java: /
นาธานเมอร์ริลล์

คำตอบ:


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()ฟังก์ชันในแต่ละวิธี
Nathan Merrill

ฉันเขียนคำตอบใหม่แล้ว อย่างไรก็ตามตอนนี้ฉันรู้ว่าการใช้รูปแบบซุ้มอาจเป็นวิธีที่จะไปที่นี่โดยการห่อรายการที่ไม่ใช่ Java ลงในสิ่งที่ดูเหมือนรายการ Java ดังนั้นจึงไม่มีการหลอกลวงนักสื่อสารหรือไตร่ตรอง
ZeroOne

2
"ตกลงดังนั้นสิ่งต่าง ๆ ที่เพิ่มขึ้นและฉันก็ลงเอยด้วยสิบคลาสต่อไปนี้" ถ้าฉันมีนิกเกิล ...
แจ็ค

โอ้วววตาฉัน! อย่างไรก็ตามเราสามารถรับแผนภาพคลาสเพื่อไปกับ 10 คลาสเหล่านี้ได้หรือไม่ หรือคุณยุ่งเกินไปที่จะเขียนคำตอบรูปแบบซุ้มของคุณ?
candied_orange

@CandiedOrange ตามความเป็นจริงฉันคิดว่าฉันใช้เวลากับคำถามนี้ไปแล้ว ฉันหวังว่าคนอื่นจะให้วิธีการแก้ปัญหารุ่นของพวกเขาอาจใช้รูปแบบซุ้ม
ZeroOne
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.