Рубрики
Без рубрики

Копирование объектов неизвестного класса в Java требует использования отражения. Это отвратительно.

В моем курсе “Проектирование и архитектура программного обеспечения” в настоящее время мы изучаем шаблоны проектирования, такие как th… Помеченный java.

В моем курсе “Проектирование и архитектура программного обеспечения” в настоящее время мы изучаем шаблоны проектирования, такие как шаблон Command . Из Википедии:

… объект command используется для инкапсуляции всей информации, необходимой для выполнения действия или запуска события в более позднее время. Эта информация включает имя метода, объект, которому принадлежит метод, и значения для параметров метода.

Вот UML-диаграмма . Цель состоит в том, чтобы обеспечить гибкость, чтобы приемник мог обрабатывать множество различных команд без необходимости их изменения. Примером, приведенным для нашего задания, является “универсальный” пульт дистанционного управления для различных бытовых приборов: как производитель пульта дистанционного управления интегрируется с различными приборами разных производителей?

Решение состоит в том, чтобы предоставить интерфейс, к которому производители устройств могут адаптировать свое программное обеспечение и инкапсулировать команды в виде объектов, передаваемых клиенту. Наше проектное задание включало реализацию демо-версии с кнопкой “отменить”, способной отменить несколько действий подряд. Вот тут-то и возникает головная боль, связанная с Java:

Естественно, я создал стек “Команд”, и при каждом вызове CommandName.execute() имя команды помещается в стек, и если пользователь нажимает “отменить”, команда выводится для вызова CommandName.undo() :

public void onButtonWasPushed(int slot) {
    onCommands[slot].execute();
    undoCommands.push(onCommands[slot]);
}

Вот тест-драйвер, предоставленный нашим инструктором:

    RemoteControlWithUndo remoteControl = new RemoteControlWithUndo();

    CeilingFan ceilingFan = new CeilingFan("Living Room");

    CeilingFanLowCommand ceilingFanLow = 
            new CeilingFanLowCommand(ceilingFan);
    CeilingFanMediumCommand ceilingFanMedium = 
            new CeilingFanMediumCommand(ceilingFan);
    CeilingFanHighCommand ceilingFanHigh = 
            new CeilingFanHighCommand(ceilingFan);
    CeilingFanOffCommand ceilingFanOff = 
            new CeilingFanOffCommand(ceilingFan);

    remoteControl.setCommand(0, ceilingFanLow, ceilingFanOff);
    remoteControl.setCommand(1, ceilingFanMedium, ceilingFanOff);
    remoteControl.setCommand(2, ceilingFanHigh, ceilingFanOff);

    System.out.println("4 on buttons");
    remoteControl.onButtonWasPushed(1); //ceiling fan goes to medium
    remoteControl.onButtonWasPushed(0); // ->low
    remoteControl.onButtonWasPushed(2); // ->high
    remoteControl.onButtonWasPushed(1); // ->medium
    // undo stack has 
    //   ceilingFanMedium at top
    //   ceilingFanHigh  
    //   ceilingFanLow 
    //   ceilingFanMedium at bottom

    System.out.println("undo command");
    remoteControl.undoButtonWasPushed(); // go back to high
    System.out.println("redo command");
    remoteControl.redoButtonWasPushed(); // return to medium

    // undo stack has 
    //   ceilingFanMedium at top
    //   ceilingFanHigh  
    //   ceilingFanLow 
    //   ceilingFanMedium at bottom     

    System.out.println("undo 4 commands");
    remoteControl.undoButtonWasPushed(); // go back to high
    remoteControl.undoButtonWasPushed(); // go back to low
    remoteControl.undoButtonWasPushed(); // go back to medium
    remoteControl.undoButtonWasPushed(); // SHOULD go back to off but does not in my implementation

Теперь, конечно, это не дало ожидаемого результата, потому что в коде приложения драйвера мы передаем тот же экземпляр объекта command в стек каждый раз, когда мы выбираем определенную настройку (вместо нового экземпляра этой команды), и команда знает, как отменить() сам основан на непубличном поле, в котором хранится предыдущая команда:

CeilingFan ceilingFan;
int prevSpeed;

public CeilingFanMediumCommand(CeilingFan ceilingFan) {
    this.ceilingFan = ceilingFan;
}

public void execute() {
    prevSpeed = ceilingFan.getSpeed();
    ceilingFan.medium();
}

public void undo() {
    if (prevSpeed == CeilingFan.HIGH) {
        ceilingFan.high();
    } else if (prevSpeed == CeilingFan.MEDIUM) {
        ceilingFan.medium();
    } else if (prevSpeed == CeilingFan.LOW) {
        ceilingFan.low();
    } else if (prevSpeed == CeilingFan.OFF) {
        ceilingFan.off();
    }
}

Таким образом, результатом последнего вызова remote control.кнопка отмены была нажата() в драйвере, чтобы вернуться к настройке “high”, потому что ссылка на команду в bottom стека указывает на тот же экземпляр, что и команда, на которую ссылается top из стека! Эта команда пытается “отменить()” что-то, что уже было отменено.

Я понимаю, что каждый раз мне нужен новый экземпляр Вызывается CommandName.execute() , поэтому я ищу метод Java clone(), где клонируемый объект должен реализовать свою собственную глубокую копию. К сожалению, для этого требуется изменить код поставщика, к которому удаленный производитель не будет иметь доступа. Я мог бы попробовать конструктор copy вместо этого, но это опять же предполагает, по крайней мере, знание кода поставщика. Если я хочу сохранить свой код разделенным или я не знаю деталей реализации поставщика, то я не могу полагаться на создание нового объекта и прямое копирование каждого поля.

С чем это нас оставляет? Ну, я понимаю, что мой единственный вариант, похоже, заключается в использовании Java reflection API и… Мне это не нравится. Я, очевидно, новичок, так что поделитесь своими мыслями, есть ли лучший способ копировать неизвестные объекты в Java или отражение все-таки не такое запутанное?

В Java нет общего механизма для этого. Чтобы класс разрешал копирование объектов, он должен реализовать механизм копирования (например, Cloneable).

В принципе, я полагаю, что можно было бы копировать объекты с помощью отражения, выбирая по одному элементу за раз и создавая копию, но заставить это работать будет сложно. Обратите внимание, что это означает, что вы также должны получить доступ к закрытым участникам. И даже если вам это удастся, нет никакой гарантии, что это будет работать так, как вы ожидаете, для классов, которые вы не контролируете…

Оригинал: “https://dev.to/weswpg/copying-objects-of-unknown-class-in-java-requires-using-reflection-it-s-hideous-5eo6”