В моем курсе “Проектирование и архитектура программного обеспечения” в настоящее время мы изучаем шаблоны проектирования, такие как шаблон 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”