在翻看《重构-改善既有代码的设计》这本经典的书,书中就介绍了一个重构方法--Duplicate Observed Data 复制被监视数据的重构方法,使用这种方法能够使界面和对数据的操作隔离,去高度耦合。这样方便平台移植。
网上也有这个方法的介绍,大多在抄书,抄写其中的文字,给出的代码也不是一个完整工程,我试着写出整个工程,整理出重构前和重构后的代码。
重构前的完整例子是这样的,尽量保持与书中代码一致。
package nelson.io; import java.awt.Frame; import java.awt.Label; import java.awt.TextField; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.FocusEvent; import java.awt.event.FocusListener; import java.awt.event.TextEvent; import java.awt.event.TextListener; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import javax.swing.Box; public class MainFrame{ private Frame f = new Frame("测试"); private TextField beginField = new TextField(""); private TextField endField = new TextField(""); private TextField lengthField = new TextField(""); private Label beginLabel = new Label("Start:"); private Label endLabel = new Label("End:"); private Label lengthLabel = new Label("Length:"); //定义水平摆放组件的Box对象 private Box horizontal1 = Box.createHorizontalBox(); private Box horizontal2 = Box.createHorizontalBox(); private Box horizontal3 = Box.createHorizontalBox(); private Box vertical1 = Box.createVerticalBox(); public static void main(String [] args) { new MainFrame().init(); } class SymFocus extends java.awt.event.FocusAdapter { public void focusLost(FocusEvent e) { Object obj = e.getSource(); if(obj == beginField) { beginField_lostFocus(e); } else if(obj == endField) { endField_lostFocus(e); } else if(obj == lengthField) { lengthField_lostFocus(e); } } } private boolean isNotInteger(String strNum) { try { Integer.parseInt(strNum); return false; } catch (NumberFormatException e) { return true; } } public void beginField_lostFocus(FocusEvent e) { if(isNotInteger(beginField.getText())) beginField.setText("0"); calculateLength(); } public void endField_lostFocus(FocusEvent e) { if(isNotInteger(endField.getText())) endField.setText("0"); calculateLength(); } public void lengthField_lostFocus(FocusEvent e) { if(isNotInteger(lengthField.getText())) lengthField.setText("0"); calculateEnd(); } /* * 初始化界面 */ public void init() { beginField.addFocusListener(new SymFocus()); endField.addFocusListener(new SymFocus()); lengthField.addFocusListener(new SymFocus()); horizontal1.add(beginLabel); horizontal1.add(beginField); horizontal2.add(endLabel); horizontal2.add(endField); horizontal3.add(lengthLabel); horizontal3.add(lengthField); vertical1.add(horizontal1); vertical1.add(horizontal2); vertical1.add(horizontal3); f.add(vertical1); f.pack(); f.setSize(300, 120); f.setVisible(true); f.addWindowListener(new WindowAdapter(){ public void windowClosing(WindowEvent e) { System.exit(0); } }); } /** * 计算结束的值 */ private void calculateEnd() { try{ int begin=Integer.parseInt(this.beginField.getText()); int length=Integer.parseInt(this.lengthField.getText()); int end=length+begin; this.endField.setText(String.valueOf(end)); } catch(java.lang.NumberFormatException e) { this.beginField.setText(String.valueOf(0)); this.endField.setText(String.valueOf(0)); this.lengthField.setText(String.valueOf(0)); } } /* *计算长度的值 */ private void calculateLength() { try{ int begin=Integer.parseInt(this.beginField.getText()); int end=Integer.parseInt(this.endField.getText()); int length=end-begin; this.lengthField.setText(String.valueOf(length)); } catch(java.lang.NumberFormatException e) { this.beginField.setText(String.valueOf(0)); this.endField.setText(String.valueOf(0)); this.lengthField.setText(String.valueOf(0)); } } }
程序运行结果如下,大致完善。
下面再给出重构后的代码:
package nelson.io; import java.awt.Frame; import java.awt.Label; import java.awt.TextField; import java.awt.event.FocusEvent; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.util.Observable; import java.util.Observer; import javax.swing.Box; public class MainFrame implements Observer{ private Frame f; //控件 private TextField beginField; private TextField endField; private TextField lengthField; private Label beginLabel; private Label endLabel; private Label lengthLabel; //定义水平摆放组件的Box对象 private Box horizontal1 = Box.createHorizontalBox(); private Box horizontal2 = Box.createHorizontalBox(); private Box horizontal3 = Box.createHorizontalBox(); private Box vertical1 = Box.createVerticalBox(); //内部模型类对象 private Interval _subject; public static void main(String [] args) { new MainFrame().init(); } //构造器 public MainFrame() { f = new Frame("测试"); beginLabel = new Label("Start:"); endLabel = new Label("End:"); beginField = new TextField(""); endField = new TextField(""); lengthField = new TextField(""); lengthLabel = new Label("Length:"); _subject = new Interval(); _subject.addObserver(this); update(_subject,null); } public void update(Observable o, Object arg) { endField.setText(_subject.getEnd()); beginField.setText(_subject.getBegin()); lengthField.setText(_subject.getLength()); } public String getEnd() { return _subject.getEnd(); } public void setEnd(String end) { _subject.setEnd(end); } public String getBegin() { return _subject.getBegin(); } public void setBegin(String begin) { _subject.setBegin(begin); } public String getLength() { return _subject.getLength(); } public void setLength(String length) { _subject.setLength(length); } class SymFocus extends java.awt.event.FocusAdapter { public void focusLost(FocusEvent e) { Object obj = e.getSource(); if(obj == beginField) { beginField_lostFocus(e); } else if(obj == endField) { endField_lostFocus(e); } else if(obj == lengthField) { lengthField_lostFocus(e); } } } private boolean isNotInteger(String strNum) { try { Integer.parseInt(strNum); return false; } catch (NumberFormatException e) { return true; } } public void beginField_lostFocus(FocusEvent e) { if(isNotInteger(beginField.getText())) setBegin("0"); else setBegin(beginField.getText()); _subject.calculateLength(); } public void endField_lostFocus(FocusEvent e) { if(isNotInteger(endField.getText())) setEnd("0"); else setEnd(endField.getText()); _subject.calculateLength(); } public void lengthField_lostFocus(FocusEvent e) { if(isNotInteger(lengthField.getText())) setLength("0"); else setLength(lengthField.getText()); _subject.calculateEnd(); } /* * 初始化界面 */ public void init() { beginField.addFocusListener(new SymFocus()); endField.addFocusListener(new SymFocus()); lengthField.addFocusListener(new SymFocus()); horizontal1.add(beginLabel); horizontal1.add(beginField); horizontal2.add(endLabel); horizontal2.add(endField); horizontal3.add(lengthLabel); horizontal3.add(lengthField); vertical1.add(horizontal1); vertical1.add(horizontal2); vertical1.add(horizontal3); f.add(vertical1); f.pack(); f.setSize(300, 120); f.setVisible(true); f.addWindowListener(new WindowAdapter(){ public void windowClosing(WindowEvent e) { System.exit(0); } }); } } class Interval extends Observable { private String _end = "0"; private String _begin = "0"; private String _length = "0"; public String getEnd() { return _end; } public void setEnd(String end) { _end = end; setChanged(); notifyObservers(); } public String getBegin() { return _begin; } public void setBegin(String begin) { _begin = begin; setChanged(); notifyObservers(); } public String getLength() { return _length; } public void setLength(String length) { _length = length; setChanged(); notifyObservers(); } public void calculateEnd() { try { int begin = Integer.parseInt(getBegin()); int length = Integer.parseInt(getLength()); int end = length + begin; setEnd(String.valueOf(end)); } catch (java.lang.NumberFormatException e) { } } /* * 计算长度的值 */ public void calculateLength() { try { int begin = Integer.parseInt(getBegin()); int end = Integer.parseInt(getEnd()); int length = end - begin; setLength(String.valueOf(length)); } catch (java.lang.NumberFormatException e) { } } }
总结一下重构过程:
1、界面中的文本框元素与中间类中的文本数据一一对应,也就是Duplicate Observerd Data。
2、界面类中的数据赋值与取值函数全部委托给中间类,当然对数据计算肯定也是在中间类中完成的,界面类根本不需要知道中间类中计算过程的存在,界面类只复制界面的显示。
3、中间类中数据的更新需要通知界面类,这里使用了Java的Observer模式。相当于界面在中间类中注册了一个回调函数。
上述代码依然可以再次重构,比如中间类Interval名称就应该改为数据模型类MainFramModel(针对MainFrame界面的数据模型model)。另外,文本框内容变动时的响应函数里,在响应函数里做了对输入规范(要求是数据)的判断,其实依然可以交给数据模型类来处理,相对于给数据模型类的元素赋值函数处理时的输入数据校验,这样界面类更简洁更纯粹。另外,文本框内容的变动可能由网络数据更新(或者其他渠道更新),这样数据模型类就应该申明成public型,作为一个单独的文件,与界面类的隔离更彻底。
再次整理后的代码如下:
界面类:
package nelson.io; import java.awt.Frame; import java.awt.Label; import java.awt.TextField; import java.awt.event.FocusEvent; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.util.Observable; import java.util.Observer; import javax.swing.Box; public class MainFrame implements Observer{ private Frame f; //控件 private TextField beginField; private TextField endField; private TextField lengthField; private Label beginLabel; private Label endLabel; private Label lengthLabel; //定义水平摆放组件的Box对象 private Box horizontal1 = Box.createHorizontalBox(); private Box horizontal2 = Box.createHorizontalBox(); private Box horizontal3 = Box.createHorizontalBox(); private Box vertical1 = Box.createVerticalBox(); private MainFrameModel _datamodel; //对应界面的数据模型 //构造器 public MainFrame() { f = new Frame("测试"); beginLabel = new Label("Start:"); endLabel = new Label("End:"); beginField = new TextField(""); endField = new TextField(""); lengthField = new TextField(""); lengthLabel = new Label("Length:"); _datamodel = new MainFrameModel(); _datamodel.addObserver(this); update(_datamodel,null); } public void update(Observable o, Object arg) { endField.setText(_datamodel.getEnd()); beginField.setText(_datamodel.getBegin()); lengthField.setText(_datamodel.getLength()); } public static void main(String [] args) { new MainFrame().init(); } public String getEnd() { return _datamodel.getEnd(); } public void setEnd(String end) { _datamodel.setEnd(end); } public String getBegin() { return _datamodel.getBegin(); } public void setBegin(String begin) { _datamodel.setBegin(begin); } public String getLength() { return _datamodel.getLength(); } public void setLength(String length) { _datamodel.setLength(length); } private void calculateLength() { _datamodel.calculateLength(); } private void calculateEnd() { _datamodel.calculateEnd(); } class SymFocus extends java.awt.event.FocusAdapter { public void focusLost(FocusEvent e) { Object obj = e.getSource(); if(obj == beginField) { setBegin(beginField.getText()); calculateLength(); } else if(obj == endField) { setEnd(endField.getText()); calculateLength(); } else if(obj == lengthField) { setLength(lengthField.getText()); calculateEnd(); } } } /* * 初始化界面 */ public void init() { beginField.addFocusListener(new SymFocus()); endField.addFocusListener(new SymFocus()); lengthField.addFocusListener(new SymFocus()); horizontal1.add(beginLabel); horizontal1.add(beginField); horizontal2.add(endLabel); horizontal2.add(endField); horizontal3.add(lengthLabel); horizontal3.add(lengthField); vertical1.add(horizontal1); vertical1.add(horizontal2); vertical1.add(horizontal3); f.add(vertical1); f.pack(); f.setSize(300, 120); f.setVisible(true); f.addWindowListener(new WindowAdapter(){ public void windowClosing(WindowEvent e) { System.exit(0); } }); } }
数据模型类:
package nelson.io; import java.util.Observable; public class MainFrameModel extends Observable{ private String _end = "0"; private String _begin = "0"; private String _length = "0"; public MainFrameModel() { } public String getEnd() { return _end; } public void setEnd(String end) { int input=0; try { input = Integer.parseInt(end); } catch(NumberFormatException e) { input = 0; } _end = input+""; setChanged(); notifyObservers(); } public String getBegin() { return _begin; } public void setBegin(String begin) { int input=0; try { input = Integer.parseInt(begin); } catch(NumberFormatException e) { input = 0; } _begin = input+""; setChanged(); notifyObservers(); } public String getLength() { return _length; } public void setLength(String length) { int input=0; try { input = Integer.parseInt(length); } catch(NumberFormatException e) { input = 0; } _length = input+""; setChanged(); notifyObservers(); } public void calculateEnd() { int begin = Integer.parseInt(getBegin()); int length = Integer.parseInt(getLength()); int end = length + begin; setEnd(String.valueOf(end)); } public void calculateLength() { int begin = Integer.parseInt(getBegin()); int end = Integer.parseInt(getEnd()); int length = end - begin; setLength(String.valueOf(length)); } }
整理完毕。