求知 文章 文库 Lib 视频 iPerson 课程 认证 咨询 工具 讲座 Modeler   Code  
会员   
 
  
 
 
     
   
分享到
重构到模式系列-重构到组合模式
 

作者:横刀天笑,发布于2012-1-13

 

在上一篇(你可以点击这里查看上一篇)中我描述了我的项目中如何从一般化设计重构到桥接模式,发表后我一直在想,我是完成那个未完成的《设计模式初学者系列》还是就此终止,开始一个新的旅程?左思右想,还是开始一个新的系列吧,设计模式的文章园子里已经有很多很多了,我是无能如何也不可能超过那些前辈的。所以我还是以重构的步伐来讲述我在实际项目中应用模式的历程。如果你在文中找到了自己的影子,不要见怪,文中所有的项目案例,设计,重构步骤都来源于我真实的项目,我只是事后把他用文字记录下来了而已。

那好吧,开始我们今天的重构之旅吧:重构到组合模式

场景

还是啰嗦两句,我觉得任何设计脱离了实际的业务环境那完全是扯淡,所以我在这里还是费一些笔墨说说我们的业务。

在石油开发中各种图件是石油工程师用来分析的非常重要的资料。我们今天要做的一个东西就是“产液剖面图”,如下图所示:

左边的圆柱体代表着一口“油井”,圆柱体上面的红色块代表一个“层位”,油井里面的油和水就是从这些层位渗透到井里面,然后抽上地面的。而右边两张表格

上面画出的是两个测试时间,各个油层产油,产水的一个情况,井和表格上面的坐标是一一对应的,这样石油工程师拿到这个图就可以清楚的看到井上的某个层位

在某个时间产油、产水是多少,而相应的做出一些决策。

需求说完了,来谈谈我的设计思路变迁吧。

第一步: 最初的设想

拿到需求后,我的第一反应是有没有现成的控件?在上一篇文章中我提到了Dundas Chart这个控件,看起来好像能完成这个任务,经过几次试验我还是失败了。

这也许是大多数人拿到需求后的第一反应,找一找是否有参考的东西。

第二步: 混乱的开始

好吧,既然找不到这样的控件那我就自己做了,我在纸上写下了我的步骤:

1.在这个UserControl里放一个PictureBox控件

2.然后建立一个Chart控件,这个控件用来管理绘图区域,坐标转换等,这样我们就有了统一的坐标系。将这个图的标题,还有右下角的图例也放在这个Chart里,

我认为这是公共的东西。

3.然后下面就是画图了,写几个方法,按顺序画图:

//PictureBox的OnPaint事件触发时执行

private void mainPicBox_Paint(object sender, PaintEventArgs e)

{

    Draw(e.Graphics);

}

private void Draw(Graphics graphics)

{

    DrawWell(graphics);

    DrawSection(graphics);

}

//绘制油井

private void DrawWell(Graphics graphics)

{

    //绘制油井的方法实现

}

//绘制油井上的层位(红色块)

private void DrawSection(Graphics graphics)

{

    //绘制层位的方法实现

}

//绘制网格

private void DrawGrid(Graphics graphics)

{

    //

}

//....等等更多绘制方法

我心惊胆战的编写着代码,生怕哪个地方把代码逻辑弄错了。上面的代码是我写出的第一个版本,

图画出来了,而且还不错,任务是完成了。

但是编写代码时的体验并不是很好,不知道大家有没有相同的感受,在使用面向过程的方式编写

代码的时候大脑必须保持足够的清晰,总是记着那条主线,而且如果一天之内没有把这个任务完

成,如果拖延到第二天,总是要看半天才能进入状态。但是使用OO的方式就不一样了,在设计的

时候把各个对象的职责划分清楚,那我们在一个时候关注的面就很小,每次只要完成一些东西就

可以了,对其他部分没有多大影响。 OK,上面的代码已经可以运行了,而且运行的很好,我为啥还要重构他呢,本来我可以就此搁笔,

这篇文章也没有必要写下去了,我不是个完全的完美主义者,只要代码不是特别的“恶心”,我

不会对他作什么更多的修改。但是无奈,我的图在PM的眼里并不是如我所看的那么完美,在这个版本中,我并没有画上一些文

字标注,比如各个产油,产水的bar后面的数字,如是我又找到那个方法,看呀看呀,找到可以

修改的地方。如果就是这些小修小补我也可以忍受。ok了,工作完成了,我把代码CheckIn到

TFS,然后CheckOut我的下一个WorkItem:注水剖面绘制,看了需求说明上的简图,怎么和

这个“产液剖面”如此的相像呢,在确认没有弄错之后,我陷入了沉思,我是拷贝那里的代码,

还是怎么做?虽然我并不是追求绝对的完美,但是在我眼里容不得重复的代码,而且这里拷贝代

码也需要做很多修改,虽然两个模块的图形非常类似,绘制图形所需要的数据则很大的不同,这

个好说,我可以用泛型解决。还有一些地方绘制的方式也不一样,不过大体上的绘制步骤应该是

一样的,我一想,这不是模板方法么?

模板方法:父类定义算法的骨架,将算法中的一些步骤推迟到子类实现。(GOF)

第三步:重构到模板方法 但是由于刚才并没有设计良好,几个方法之间耦合性很强,而且方法里面的逻辑混乱,我费了好

大的劲才弄成模板方法的,代码是比拷贝少了很多,但貌似还是一种过程式的,这次重构完全是

为了整体上看起来代码少些,对程序的结构没有任何改良。

至于模板方法模式是个什么样子的,我这里就不给出例子了,园子里已经有很多相关经典的文章,

不过你也可以看我的这篇:

设计模式初学者系列-模板方法

还是一样CheckIn代码,CheckOut WorkItem,一看怎么还是什么XXX剖面,再看看所有的

WorkItems,好多剖面的绘制啊,功能也大同小异,难道我就这样用模板方法一个的实现?在上

一篇文章里我们就谈到了,继承这种白箱复用有很多局限性,子类有一丁点与父类不同我们就必

须重写,如果这么多模块都要用模板方法实现,那肯定要不断的调整,有没有什么更好的方法? 第四步:重构到组合模式 再去看一眼那个图,难道他就是铁板一块么?我们可以把他分成很多小对象,每个对象管理自己

的数据并且绘制自己,在这个图里面有哪些可以划分的对象呢?

Well //油井

Section //层位

Grid //网格

Axis //坐标轴

Text //图上的文本

....其他一些对象

每个对象都应该有坐标数据,还应该有一个绘制的方法,对象自己绘制自己,其他的事情都无需

关心。这里引出了OO的另外一个原则:

对象应该对自己负责,而且职责单一

设计完几个类后,突然有种感觉,这个图怎么样一颗对象树啊,图里面有油井,网格,油井里面

有层位,网格里面有坐标轴等东西,难道我们要和这一个个对象?想一想,哪一个模式是将对象

组织成树结构一样?她就是今天的主角-组合模式,或者叫部分-整体模式 组合模式:将对象组合成树形结构以表示“部分-整体”的层次结构。Composite模式使得用

户对单个对象和组合对象的使用具有一致性。【GOF】

设计如下图所示:

部分代码:

public class UIElement

{

    private IList<UIElement> Children;

    public UIElement()

    {

        this.Children = new List<UIElement>();

    }

    public float X

    {get;set;}

    public float Y

    {get;set;}

    public void Add(UIElement item)

    {

        this.Children.Add(item);

    }

    public void Draw(Graphics graphics)

    {

        foreach(UIElement item in this.Children)

        {

            item.Draw(graphics);

        }

    }

}

public class Chart : UIElement

{

    public void Add(UIElement item)

    {

        //在chart里我们要处理一些坐标变换等东西,

        //所以这里的Add方法需要重写

    }

}

public class Well : UIElement

{

    public Well():base()

    {}

    public float Width

    {get;set;}

    public float Height

    {get;set;}

    public void Draw(Graphics graphics)

    {

        //绘制油井代码实现

        base.Draw(graphics);

    }

}

//.................

好了吧,用这样的设计,在同一时刻你只需要考虑一个很小的对象如何去绘制,而且也非常好扩展,如果有更多的东西需要绘制在界面上,只需要从UIElement继承

就可以了,客户调用的时候只需要构建一个Chart对象,向其Children里添加子对象,然后调用其Draw方法就可以绘制了。在这里如果构建Chart对象呢?我们可以使用一个创建型模式解决。

后记

实际上一般绘图程序都是使用组合模式来解决,但是我开始的时候并没有认识到这个绘图的东西需要多处复用,所以想以最容易想到的方式做掉,但往往最容易想到的方式并不是最简单和最好的方式。所以也有了这篇文章。

本文只是记述重构的旅程,并没有详细描述GUI绘图的相关知识,也没有对组合模式更多的细节进行介绍,我这里想表达的只是一个设计思路,如果你想更多的了解组合模式你可以点击

这里 查看TerryLee的设计模式系列中的组合模式

祝你编程愉快


相关文章

企业架构、TOGAF与ArchiMate概览
架构师之路-如何做好业务建模?
大型网站电商网站架构案例和技术架构的示例
完整的Archimate视点指南(包括示例)
相关文档

数据中台技术架构方法论与实践
适用ArchiMate、EA 和 iSpace进行企业架构建模
Zachman企业架构框架简介
企业架构让SOA落地
相关课程

云平台与微服务架构设计
中台战略、中台建设与数字商业
亿级用户高并发、高可用系统架构
高可用分布式架构设计与实践
 
分享到
 
 
     


重构-使代码更简洁优美
Visitor Parttern
由表及里看模式
设计模式随笔系列
深入浅出设计模式-介绍
.NET中的设计模式
更多...   

相关培训课程

J2EE设计模式和性能调优
应用模式设计Java企业级应用
设计模式原理与应用
J2EE设计模式指南
单元测试+重构+设计模式
设计模式及其CSharp实现


某电力公司 设计模式原理
蓝拓扑 设计模式原理及应用
卫星导航 UML & OOAD
汤森路透研发中心 UML& OOAD
中达电通 设计模式原理
西门子 嵌入式设计模式
更多...