软件开发过程中,唯一不变的就是变化。这是一句老生常谈,也就是说软件开发中永恒的主题就是变化。当你把代码都写好了,测试也完成了,准备交付的时候客户忽然要求你在指定时间做出变化,这种情况在外包行业中很常见;而对一些银行金融项目,边调研边开发边测试屡见不鲜;对于一些Web项目,从来就只有永远的Beta版,改来改去的事更是家常便饭。对此,程序员一定要求清晰的认识,抱怨只能是嘴上痛快,不解决实际问题。真要解决实际问题,非要动一番脑筋不可,如果合理使用了设计模式,反射或是Spring的IoC,便能变修改噩梦为一次有趣的智慧之旅。
首先我们看原始要求:客户要求将一批雇员名单存入到CSV和XML两种文件中去,以后还有可能增加别的文件格式,比如PDF,XLS等,虽然这是下一期的内容,但这一期应该考虑到变化,客户要求扩展性一定要好。
没问题,有了设计模式响应变化不难。这时我们可以用到模板方法模式:
定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。
先请看骨架抽象类:
- public abstract class FileMaker {
-
-
-
- private List<Employee> employees;
-
-
-
-
-
-
-
- public final void makeFile(List<Employee> employees,String fileName){
- setEmployees(employees);
- makeFile(fileName);
- }
-
-
-
-
-
- protected abstract void makeFile(String fileName);
-
- public final void setEmployees(List<Employee> employees) {
- this.employees = employees;
- }
-
- public List<Employee> getEmployees() {
- return employees;
- }
- }
很好,固定的函数和步骤都在抽象基类中写定了,再看两个具体实现类,它们要实现的就是makeFile函数而已。
- public class CSVFileMaker extends FileMaker{
- protected void makeFile(String fileName){
- try {
- BufferedWriter out = new BufferedWriter(new FileWriter(fileName));
-
- for(Employee emp:getEmployees()){
- String line="";
- line+=emp.getName()+",";
- line+=(emp.isMale()?"男":"女")+",";
- line+=emp.getAge()+",";
-
- out.write(line+"\r\n");
- }
-
- out.close();
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- }
- public class XMLFileMaker extends FileMaker{
- protected void makeFile(String fileName){
- try {
- Document document = DocumentHelper.createDocument();
- Element root = document.addElement("employees");
-
- for(Employee emp:getEmployees()){
- Element empElm=root.addElement("employee");
-
- Element nameElm=empElm.addElement("name");
- nameElm.setText(emp.getName());
-
- Element sexElm=empElm.addElement("sex");
- sexElm.setText(emp.isMale()?"男":"女");
-
- Element ageElm=empElm.addElement("age");
- ageElm.setText(String.valueOf(emp.getAge()));
- }
-
- OutputFormat format = OutputFormat.createPrettyPrint();
- format.setEncoding("GBK");
- XMLWriter writer = new XMLWriter(new FileWriter(fileName),format);
-
- writer.write(document);
- writer.close();
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- }
这样昨完以后感觉很好,因为我们成功的把变化和不变分离开来,不变的部分放在了抽象基类中,而容易变化的部分放在了两个具体的子类中,这样如果再增加一种新文件格式,从抽象基类再扩展出一个子类即可。很好,这样就不怕变化了。客户对此也没有异议。
调用示例如下:
- List<Employee> emps=new ArrayList<Employee>();
- emps.add(new Employee("Andy",true,21));
- emps.add(new Employee("Bill",false,23));
- emps.add(new Employee("Cindy",true,25));
- emps.add(new Employee("Douglas",false,28));
-
- FileMaker fileMaker=new CSVFileMaker();
- fileMaker.makeFile(emps, "1.csv");
-
- fileMaker=new XMLFileMaker();
- fileMaker.makeFile(emps, "2.xml");
客户看到了我们的调用的例子,觉得应该更灵活一些,他说存成各种不同的文件是通过点击按钮来实现的,如果每个按钮的事件处理函数都要生成具体子类岂不是太死板了吗?这样做每个文件下载按钮的事件处理代码不是都不一样?
有点道理,如今理解到这一层的客户实在是不多见了。不过很容易满足他的需求,我们可以引入反射的方法:
- public static void main(String[] args) {
- List<Employee> emps=new ArrayList<Employee>();
- emps.add(new Employee("Andy",true,21));
- emps.add(new Employee("Bill",false,23));
- emps.add(new Employee("Cindy",true,25));
- emps.add(new Employee("Douglas",false,28));
-
- callByReflect("csv",emps,"1.csv");
- callByReflect("xml",emps,"2.xml");
- }
-
- public static void callByReflect(String type,List<Employee> emps,String fileName){
- try{
- Class cls=Class.forName("com.heyang."+type.toUpperCase()+"FileMaker");
- FileMaker fileMaker=(FileMaker)cls.newInstance();
- fileMaker.makeFile(emps, fileName);
- }
- catch(Exception ex){
- ex.printStackTrace();
- }
- }
因为按钮上的文字和类名是有关的,如下载CSV的按钮上就有CSV的文字,这可以通过正则表达式取道,再组合一下不就是类名了吗?csv到com.heyang.CSVFileMaker,xml到com.heyang.XMLFileMaker,其实变化就是三个字母而已。如果增加按钮,取出按钮中的三个字母再调用callByReflect函数即可,这个过程简直可以固化。
客户看到反射方法以后很是满意,没有意见了。待客户走后,项目经理把你拉到一边,说:
“你刚才的方法不错,确实很强,但看得懂反射并能灵活掌握的人水平要够一年经验才行,维护的活让一年经验的人去干太可惜了,最好改改,最好达到让新手也能掌握并修改的程度。”。
没办法,领导总有领导的考虑,他这么说也很合理,成本问题我可以不考虑,但如果把程序搞得复杂貌似NB,能让一些学艺不精的人产生云山雾罩的感觉,有时还能被人尊称一声“大侠”,但谁也不比谁傻多少,这声大侠不是白叫的,但是出了问题或是有了变化别人还是要找你,到头来还是给自己添乱,这些都是义务劳动,何苦来呢?还是应该改得容易些,让大家都能修改,我可不愿意半夜三更被人叫起来问问题。
用Spring的IoC就可以解决问题,写一个新类并配置到XML文件中对新手来说问题不大,这下可以让领导放心了,自己就更放心了。
IoC方案代码如下:
- public class Main {
- public static void main(String[] args) {
- List<Employee> emps=new ArrayList<Employee>();
- emps.add(new Employee("Andy",true,21));
- emps.add(new Employee("Bill",false,23));
- emps.add(new Employee("Cindy",true,25));
- emps.add(new Employee("Douglas",false,28));
-
- callByIoc("csv",emps,"1.csv");
- callByIoc("xml",emps,"2.xml");
- }
-
- public static void callByIoc(String type,List<Employee> emps,String fileName){
- try{
- ApplicationContext ctx = new FileSystemXmlApplicationContext("bean.xml");
- FileMaker fileMaker=(FileMaker)ctx.getBean(type);
- fileMaker.makeFile(emps, fileName);
- }
- catch(Exception ex){
- ex.printStackTrace();
- }
- }
- }
Bean。xml文件内容很简单吧:
- <?xml version="1.0" encoding="UTF-8"?>
- <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
- <beans>
- <bean id="csv" class="com.heyang.CSVFileMaker"/>
- <bean id="xml" class="com.heyang.XMLFileMaker"/>
- </beans>
好了。到这里问题就彻底结束了,终于满足了客户和上级的要求,可以回家睡个好觉了,不用担心别人打搅了。
态度改变一切,变化来了人总是要多做一些,心理当然是不愿意的,但抱怨或是消极抵制都不是解决问题之道;如果把它看做一个挑战的契机,凡事多思考一些,不但能解决问题,自己也会有所提高,这就是积极的态度带来的好处。