项目引入模式的案例——Excel扩展单元格

前些日子做一个Excel扩展行的机制,简单说,模板-->生成文件,模板和文件均是excel文件,其中模板有很多公式,其中要说的是这个扩展行的公式#L:${}(至于如何解析Excel,有兴趣的朋友可以去了解poi和js的犀牛引擎,ps:java项目)
具体如下图所示:
  上图模板经过解析后的结果图:

  到现在扩展行功能运行的很好,很快顶头上司跟我说了他的新想法,他想为扩展行提供分组隐藏,负数显红色等等功能支持。具体是像下面的样子:

  直觉上我感觉这个“等等”是做不完的,很明显今天说偶数行变斜体,明天就会出来单元格内容含有“张”字的变8号宋体,甚至出现某单元格类型是数字类型另外一个单元格是日期类型。该怎么去实现这些无穷尽的变化呢?解决问题前,我先介绍下我的工作性质,我是做平台开发支持的,所开发的东西都服务于其他部门人员,也即我做工具平台,别人拿来结合业务2次开发。我的客户是跟我一样的程序员。
  来想想我的客户需要什么,我需要给他们提供以下支持:单元格内容的动态变化,单元格Style(包含字体、边框等样式)变化,单元格类型变化,除此之外,还有什么地位值得注意呢?有没有发现这种变化机制和具是体单元格是松耦合的,比如“偶数行斜体”这个你可以在A1列使用,也可以在A2列使用,看来这种机制要独立可重复,同时我们还能发现,“偶数行斜体”和“负数显红色”可以叠加。
  现在问题已经很清晰了,我们需要为“某单元格”提供一个可以独立、叠加“装饰”的机制。我真的不知道除了“装饰”二字我还能用什么词语更好的形容这种机制,坦言当时我的脑海里已经浮现了“装饰模式”,也许这是很自然的,因为问题已经被分析用“装饰”词语去描述。
  本文不打算拿大段文字去描述何为装饰模式,也许你可以到这里去了解它装饰模式。我们知道Decorator Pattern意图是:动态地给一个对象添加一些额外的职责。从前面的分析我们可以看到我们确实需要动态地给一个对象(单元格对象)添加额外的职责(让它变红,变斜体等等)。
  你是否同意我用装饰模式呢?这里插入一段我和我们经理的讨论:(我老大是个比较强势的人,一般喜欢用自己的经验去压制你,让你按他的想法去实现,呵呵,我习惯了)。
  正当我对自己画出的草图happy的时候,经理过来问我打算怎么做,我说了下自己的想法,他想了下说,他有个办法,他说首先提供一个单元格信息的context(上下文)其内容包含CellType、CellValue、CellStyle等等,然后做一个管道,管道每个节点有一个get和set方法,最后让CellContext(某一个上下文)依次经过每个节点,改变其状态,最后通过上下文取得CellType、CellValue、CellStyle。
  不能不说下我老大已是有10年的开发经验牛人,很多时候我都是按他的思路去实现,对于老大刚刚说的办法我感觉不错,可当时我脑子里都是装饰模式,我不想放弃自己的方案,他看出了我的不愉快,接着他介绍到这就和监听模式一样,我疑问到“监听模式?是观察者模式么?”老大说一个context被一个个监听然后改变,我很怀疑老大是不是搞错了,因为我据我所知观察者不是为了改变状态,观察者是为某些对象建立一种“通知依赖关系”至于观察者是不是想要改变自身或者被观察者的状态那是另外一回事。从意图上看老大是没有使用好观察者,我们不是需要“当一个对象的状态发生改变时, 所有依赖于它的对象都得到通知并被自动更新”。我们需要的是如何实现“动态改变自身的状态”。
  当然最后我之言了老大对模式的理解偏差,当然我能感觉到他的管道模式确实可以解决这个问题,后来也证明了这种管道的解决方案可以提供更高的性能,我甚至怀疑管道模式是不是装饰模式的简化版。不管怎样我还是用了自己的方案。
  下面这张图是简化的解决方案类图。

  上面这个类图能看出来IExcelComponent接口是关键,其中实现次接口的DefaultCell相当于微粒其包含一个ExcelContext对象,对应到装饰模式类图,DefaultCell便是ConcreteComponent这是个被装饰的对象,我感觉叫做微粒很合适,有了微粒空气中的水蒸汽会附着凝结降下雨水。CellDecorator这里是所有修饰子类的基类。
文章开始部分谈到如何实现这无穷尽的变化---这里变化就是CellDecorator的子类变化。我打算支持做到这里,子类的编程留给其他人来做,至于使用者打算做成什么样子,我不需要考虑。
  真正写程序时你会发现自己勾勒的美好蓝图,实施的时候会遇到一些小麻烦。我就遇到一个困难——装饰子类的实例化问题,正常情况通过构造器把IExcelComponent当作参数实例化。
那么代码应该这么写:
1DefaultCell dcell=new DefaultCell(new ExcelContext("和当前单元格相关信息"));
2MergeGroupDecorator gd=new MergeGroupDecorator(dcell);
3FontColorDecorator fd=new FontColorDecorator(gd);
  上面写的是分组隐藏和颜色更改两个装饰使用的伪代码。
可以看到既然现在子类是由使用者实现,那么我无法估计到怎么实例化他,就是装饰子类的构造器我都无法知道我怎么实例化呢?真实使用模式我们也许根本无法完全套用模式的案例,也没必要完全模仿,我们只要把握模式的精髓就可以了。
既然无法预计怎么实例化那么就不去用构造器,我们自己定义set方法给CellDecorator,
1    /**设置待修饰构件*/
2    public void setComponent(IExcelComponent component) {
3        this._component = component;
4    }
  这样子类继承后,我们自然可以使用这个方法,进而避免了实例化的困窘。我们需要做的是收集所有子类的类型,可以提供一个注册装饰类的方法,装饰类集中在一个字典里,我们可以在扩展列声明每列使用哪些装饰,然后去查找对应的装饰类。
  接下来我从构建到使用给出所有部分代码和文档。
IExcelComponent   装饰基础接口:
Code
CellDecorator  装饰者基类:
Code

ExcelContext 上下文类

Code

 DefaultCell   包含上下文的被装饰类:

Code
两个装饰子类(由使用者自己编程,此处为示例)
 MergeGroupDecorator :
Code
 FontColorDecorator :
Code
注册所有装饰类
Code
注意图Excel模板的A2和D2单元格包含注释,注释里面有装饰的描述:decorators=ItalicDecorator,FontColorDecorator
根据当前单元格构造上下文:
1ExcelContext context = new ExcelContext(cellWrite,
2                    (List) cr.result, i, book, vManager, (List) cr.result);
根据上下文和当前单元格的装饰描述去已经注册的装饰字典查找然后实例化
Code
取出想要的type、value、style
1IExcelComponent iec = getComponetFormComment(vc, context);
2            Object valueTextResult = iec.getValue();
3            cellWrite.setCellStyle(iec.getStyle());
4            cellWrite.setCellType(iec.getType());
写单元格。
1setCellValue(cellWrite, book, valueTextResult, cr.showCellAsError);

  最后想说的是,刚刚插曲最后说我感觉老大的管道方案更好,是因为我发现我现在方案会导致每个单元格都要实例许多装饰对象,而我老大说的管道,只需要几个固定的节点对象可以反复使用。从这点看他的比较好,究其原因是我的装饰本身使用组合,而他的管道仅是保存引用,还有仔细想想管道方案就是装饰模式的一种,如果把上下文看着被装饰对象,管道节点为装饰对象,那么get和set方法实现装饰,接口是仅仅包含get和set上下文的方法,可巧的是他没用对象组合引用上下文而是选择了聚合上下文。

 

LoveBaoBao

作者:LoveBaoBao

出处:http://lovebaobao.cnblogs.com

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
posted @ 2009-06-12 15:49  张蒙蒙  阅读(2756)  评论(7编辑  收藏