在我的“软件设计和架构”课程中,我们目前正在学习设计模式,例如命令图案。 从维基百科:
...命令对象用于封装以后执行动作或触发事件所需的所有信息。 该信息包括方法名称,拥有方法的对象和方法参数的值。
Here's a UML diagram. The purpose is to provide flexibility so that a receiver can handle many different commands without needing to be modified. An example given for our assignment is a 'universal' remote control for various household appliances: how does the manufacturer of the remote control integrate with a variety of appliances from different manufacturers?
解决方案是提供设备制造商可以适应其软件的界面,并将命令封装为传递给客户端的对象。 我们的项目分配涉及使用“撤消”按钮实现演示,该按钮能够撤消连续的多个操作。 这是Java令人头疼的地方:
自然,我创建了一个“命令”堆栈,每次调用commandName.execute(),commandName is pushed to the stack and if the user hits 'undo',command is popped in order to call 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
现在,现在当然没有我期望的输出,因为在驱动程序应用程序代码中,我们正在传递相同的命令对象实例每次我们选择一个特定的设置(而不是该命令的新实例)时,该命令都会进入堆栈,并且该命令知道如何undo()本身基于存储先前命令的非公共字段:
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();
}
}
因此,在驱动程序中最后一次调用remotecontrol.undoButtonWasPushed()的结果是返回到“高”设置,因为在底部堆栈的指向与实例中引用的命令相同的实例最佳的堆栈! 该命令正在尝试“撤消()”已经撤消的操作。
I realize I need a new instance each time commandName.execute() is called, so I look up Java's clone() method, where the object being cloned must implement it's own deep copy. Unfortunately, this requires modifying the vendor code, which the remote manufacturer wouldn't have access to. I could try a copy constructor instead but that again assumes at least knowledge of the vendor code. If I want to keep my code decoupled or I don't know the details of the vendor implementation, then I can't rely on creating a new object and copying each field directly.
What does that leave us with? Well, I realize my only option seems to be using Java's reflection API and ... I don't like it. I'm obviously a beginner so share your thoughts, is there a better way to copy unknown objects in Java or is reflection not so convoluted after all?
Java中没有通用的机制。 对于允许复制对象的类,它应该实现一种复制机制(例如Cloneable)。原则上,我认为可以使用反射来复制对象,一次选择一个成员并构建一个副本,但是很难使其工作。 请注意,这意味着您也必须访问私有成员。 即使您成功了,也无法保证它会为您无法控制的类按预期工作……