`
fangang
  • 浏览: 876825 次
  • 性别: Icon_minigender_1
  • 来自: 北京
博客专栏
311c4c32-b171-3767-b974-d26acf661fb2
谈谈用例模型的那些事儿
浏览量:38637
767c50c5-189c-3525-a93f-5884d146ee78
一次迭代式开发的研究
浏览量:68804
03a3e133-6080-3bc8-a960-9d915ed9eabc
我们应当怎样做需求分析
浏览量:409925
753f3c56-c831-3add-ba41-b3b70d6d913f
重构,是这样干的
浏览量:91486
社区版块
存档分类
最新评论

过程扩展与放置钩子

阅读更多
前面我们谈到了功能扩展对维护一个软件的巨大作用。实际上,正是因为功能在不断地扩展,才使得我们的很多软件质量在下降。因此,如何进行功能扩展,我们不得不察。每当新功能到来的时候,不用急急匆匆就开始编码,我们应当仔细思考我们的设计,即使是时间非常紧张的项目。用更多的时间去思考与设计,才会用更少的时间去做更简单的设计与编码。在这里,我提倡的是设计应当简单到发指,因为它体现的是一种精巧绝伦,它会使我们的思路更清晰,维护更简单,变更更容易。只有经过仔细的思考,才会做出精巧绝伦的设计,那是我们的目标。在这方面,“小步快跑”与“两顶帽子”的方法可以大大降低我们的设计难度,因为我们不是神,而是人。

上一章,我们用“抽取接口”的方法,替代万恶的if语句,从而实现了功能扩展。注意,不是所有的if语句都需要这样调整,只有那些真正需要扩展的时候才应该这样调整。如果if语句只有2、3个条件分支,并且不太可能扩展,我们真的不必这样调整,或者当这样的功能扩展到来的时候再进行调整。记住,过度设计与恰到好处的设计只有一线之隔。
除此之外,我们还有很多的办法来扩展我们的功能,其中一种扩展叫过程的扩展,我们可以这样设计:

前面代码复用的部分我们提到,解决处理过程中相同或相似的代码最好的办法就是使用模板模式。首先将那些相同的代码抽取出来形成函数,将这些函数抽象并升级为抽象类及接口,然后将各自不同的代码统一函数名,放在各个实现类中各自去实现。这样,代码复用的问题就解决了。

但是,毫无疑问我们都不是先知,永远都无法预测未来系统会变成什么样儿。比较常见的需求变更之一就是处理步骤的变更。现在我们的处理有1、2、3步,而今后可能还有5、6、7步,甚至某项步骤可能会插入到现有步骤的中间。当日后这样的变更发生的时候,我们又希望符合OCP原则而不改动现有代码时,又应当怎样设计呢?嗯,是个问题。

我过去就曾无数次遇到过这样的问题,其中一个令我印象深刻的就是一次平台控件的设计。在一次平台开发中,我设计了许多的控件,如文本框、下拉框、单选框、复选框……开发这些控件的目的是使其它开发者在设计报表的过滤条件时不用再写任何代码,选择控件就可以了。起初,我为所有控件都提供了draw()和beUsed()方法,用于绘制控件和判断该条件在查询时是否被使用。随着控件品种的增加,一些控件需要在绘制前要执行一个查询,如那些多选框、下拉框等等。为此我准备设计了一个getItems()方法,只要这些控件定义了各自的查询语句,就可以通过该方法查询并返回结果。但问题是,前面已经设计好的控件不用这个方法,我不希望因为这个功能的扩展影响了前面那些控件,这该怎么设计呢?

每次面对这样的问题时,一种叫做“钩子(hook)”的设计就可以派上用场了。什么叫“钩子”?它是一个空函数,调用它就如同什么都没有调用一般。但钩子如果被放在了抽象类中,作用就非常大了。如果抽象类的子类要使用它时,则重载这个函数,为其编写各自的代码,完成相应的操作;而其它的子类如果不使用它,则什么也不用做。当系统在调用各个子类时,被重载的子类就会去调用子类中的函数,而其它没有被重载的子类则会去调用抽象类中的“钩子”,就如同什么都没有做一样。

在该示例中,getItems()就是一个“钩子”,它首先被定义在父类AbstractControl中。AbstractControl是一个抽象类,但getItems()在里面不是被定义成一个抽象方法,而是一个普通方法,因为它不需要每个子类都去实现它,不使用的子类就不用再实现它了。
	/* (non-Javadoc)
	 * @see com...control.Control#getItems(com...model.RptControl)
	 */
	public List getItems(RptControl control){
		//hook only
		return null;
	}


那些在绘制前不需要查询的控件,如DefaultControl,在继承父类的时候不用去重载getItems(),因此系统在绘制它们时,该函数就如同不存在一般。然而那些需要查询的控件,如QueryControl,就需要重载这个函数:
	/* (non-Javadoc)
	 * @see com...control.Control#getItems(com...model.RptControl)
	 */
	public List getItems(RptControl control) {
		if (control==null) {
			throw new IllegalArgumentException("参数为空");
		}
		String sql = control.getSql();
		if (sql==null||"".equals(sql)) {
			throw new RuntimeException("SQL为空");
		}
		BasicQuery query = new BasicQuery();
		query.setExpression(sql);
		return getJdbcSupport().find(query);
	}

这样,当系统在绘制DefaultControl的时候不会去查询数据库,而绘制QueryControl的时候则先去进行一个数据库查询。现在我们来检测一下该可扩展点的设计能否满足OCP原则的要求。现在新需求来了,要绘制这么一个组合控件:



这个组合控件由四个下拉框组成,分别代表2个年度与2个月份,因此它在执行查询时,会提交到后台这4个数据。然而,我们希望这个控件在提交给查询模块时,应当是2个数据:某年某月的1日,和某年某月的最后一天。也就是说,该控件在提交参数给查询模块的时候需要进行一个参数转换,而不是直接传递给查询模块。
先看看我们现有的设计吧:当控件将参数提交给后台以后,控件会直接将参数传递给查询模块。但为了实现这样一个新需求,我们需要所有控件在这个地方硬生生插入一个数据转换的功能。如果真的这样修改了,整个系统就因小失大了。幸运的是,我们在这个地方有可扩展设计。
首先,我们在抽象的父类AbstractControl中加入一个非抽象方法transform():
	/* (non-Javadoc)
	 * @see com...control.Control#transform(java.lang.String, java.util.Map)
	 */
	public void transform(String ctrlName, Map<String, Object> params){
		//hook only
	}

然后我们创建新控件MonthRangeControl,重载transform()方法:
	/* (non-Javadoc)
	 * @see com...control.Control#transform(java.lang.String, java.util.Map)
	 */
	public void transform(String ctrlName, Map<String, Object> params){
		int yearLower = getValue(ctrlName, params.get(“yearLower”));
		int monthLower = getValue(ctrlName, params.get(“monthLower”));
		int yearUpper = getValue(ctrlName, params.get(“yearUpper”));
		int monthUpper = getValue(ctrlName, params.get(“monthUpper”));
		Date lower = DateUtil.getDate(yearLower, monthLower, 1);
		Date upper = 
			DateUtil.getLastDayOfMonth(DateUtil.getDate(yearUpper, monthUpper, 1));
		params.put(ctrlName, lower);
		params.put(ctrlName, upper);
}

整个设计修改了父类AbstractControl,增加了transform()方法,然后创建了新的控件类MonthRangeControl,不能说完全没有修改原程序,但已经在最大限度上满足了OCP原则。整个设计如图:



(续)

相关文档
遗留系统:IT攻城狮永远的痛
需求变更是罪恶之源吗?
系统重构是个什么玩意儿
我们应当改变我们的设计习惯
小步快跑是这样玩的(上)
小步快跑是这样玩的(下)
代码复用应该这样做(1)
代码复用应该这样做(2)
代码复用应该这样做(3)
做好代码复用不简单
软件可以这样功能扩展
过程扩展与放置钩子

特别说明:希望网友们在转载本文时,应当注明作者或出处,以示对作者的尊重,谢谢!
  • 大小: 29.7 KB
  • 大小: 45.3 KB
分享到:
评论
1 楼 ivan19861025 2014-06-03  
基本上看完了, 写得很好, 通俗易懂. 但项目却很好的去这样实施, 只有程序员养成这个良好的习惯. 才能从根本上解决这个问题. 大部分时候"程序"是商业的, 以完成任务为主, 尤其是外包, 多一事不如少一事, 做完这个项目"程序员"可能就"闪人"了. 管不了这样多. 大部分公司现状如此. 不知道博主有什么好的建议.

相关推荐

    VisualC_6_0下局部钩子技术的应用

    当与指定的钩子类型相关联的消息发生时,系统会将这些消息传递给对应的钩子子程进行处理。最新的钩子总是放在链表的最前面,这意味着最新安装的钩子总是最先获取消息并有机会对其进行处理。 #### 1.3 钩子的范围 ...

    用VC_c全局钩子.pdf

    新安装的钩子会被放置在链表头部,这意味着这些钩子会优先处理捕获到的消息。 3. **关键API函数**: - **SetWindowsHookEx**:用于安装钩子函数。此函数需要提供钩子类型标识、钩子函数地址、包含钩子函数的动态...

    Hook钩子\发声钩子.doc

    【Hook钩子与发声钩子】技术是一种在Windows操作系统中监听和处理键盘输入的方法,它允许程序员通过安装特定类型的钩子来捕捉系统级的键盘事件。这篇文章主要介绍了如何实现一个简单的发声钩子,使得每当用户按下...

    浅谈HOOK技术在VC编程中的应用

    全局钩子的安装和卸载过程涉及到了SetWindowsHookEx函数,它将回调函数放置在钩子链表的开始位置,该函数原型声明如下: HHOOK SetWindowsHookEx(int idHook, HOOKPROC lpfn, HINSTANCE hMod, DWORD dwThreadId); ...

    史上最详细的扩展List控件制作代码

    涉及到的源代码文件,如`LzqListEx.cpp`、`LzqListExDlg.cpp`、`MsgHook.cpp`等,分别处理主程序逻辑、对话框类实现、消息钩子处理等。`LzqListEx.aps`是工程文件,`ListCtrlEx.cpp`和`ListCtrlEx.h`包含了List控件...

    API_HOOK监控删除和移动带有某字样的文件.rar

    3. **替代函数指针**:在目标API的函数指针前放置我们的钩子函数,当API被调用时,实际上是调用了我们的函数,然后由我们的函数决定是否继续调用原API。 4. **系统钩子结构**:在Windows系统中,可以使用...

    vue2扩展组件-悬浮拖拽组件

    2. **悬浮效果**:组件在拖动过程中保持在用户手指或鼠标附近,即使在滚动页面时也能保持相对位置,提供流畅的用户体验。 3. **吸附功能**:当组件靠近页面的边界或其他元素时,它会自动吸附,以优化布局。这可以...

    liferay-developer-guide-6.0

    - **Hooks(钩子)**:钩子允许开发者覆盖或扩展Liferay的默认行为。这包括修改JSP文件、服务层代码或语言文件。 - **Ext plugins(扩展插件)**:这是Liferay的一种高级扩展机制,通过创建扩展插件,开发者可以在不...

    抓取窗口图形到剪贴板 vc6 0源码

    在Windows操作系统中,鼠标钩子是一种系统级的钩子,允许开发者监控和处理与鼠标相关的事件,比如鼠标移动、按键等。贺成士的原程序可能只是一个基础的鼠标事件监听器,而这个程序则在其基础上扩展了抓图功能。 ...

    PrestaShop Module Development

    《PrestaShop 模块开发》一书主要涵盖了为PrestaShop 1.5或PrestaShop 1.6开发和定制强大模块的过程。书中不仅详细地讲解了模块开发的基础知识,还深入探讨了高级主题,如钩子、上下文对象、模块更新、前端控制器、...

    前端vue学习过程,需重新学习

    10. **生命周期钩子**:Vue组件有多个生命周期钩子函数,如`created`、`mounted`、`updated`等,允许在特定阶段执行操作。 11. **计算属性与侦听器**:计算属性用于基于其他数据动态计算出新的值,而侦听器则可以...

    Linux 2.6内核下LKM安全性研究.pdf

    具体来说,就是在`init_module`函数前后放置安全钩子函数。这样做的目的是在模块加载到内核之前和之后进行额外的安全检查,防止恶意模块在通过安全性检查后篡改诸如系统调用表(sys_call_table)、中断描述符表(IDT...

    ASCET代码生成简易说明.pdf

    此外,还包括与操作系统相关的设置,例如消息传递机制、钩子函数的创建以及OIL描述文件的生成等。 ##### 2. `target.ini` 文件 每个ASCET-SE目标都有一个特定的描述文件——`target.ini`。此文件的主要作用在于...

    神奇的马甲DLL(dll挟持技术介绍)

    3. **实现功能扩展**:在定制DLL中实现额外的功能,如函数钩子等,以达到特定目的,例如修改系统行为或监控程序活动。 #### 五、DLL挟持的案例分析 以lpk.dll为例,这是一个特殊的DLL,其中包含的数据而非代码的...

    apache 模块开发的例子

    5. 编译与安装:最后,编译模块为动态链接库(.so文件),并将其放置在Apache的modules目录下,更新配置文件以加载模块。 三、"adservice"模块示例 假设"adservice"是一个广告服务模块,它的主要任务是在返回给...

    Drupal 7模块开发

    - **钩子(Hooks)**:钩子是Drupal中一种特殊的功能,允许开发者在特定的点插入自定义代码,从而扩展或修改系统行为。例如hook_menu()用于定义菜单项。 - **APIs**:Drupal提供了一系列API,如节点API、用户API等,...

    react-enterprise-starter-kit:高度可扩展的真棒React Starter Kit,用于企业应用程序,具有易于维护的代码库

    特征: 完全基于最新的React钩子。 Redux商店已经有了传奇作为中间件。 对开发和生产版本的webpack完全控制。 随附所有eslint标准规则,以实现一致的代码库。 由于其原子设计,因此具有高度可扩展性。 具有懒惰放置...

    HOOK基础知识总结

    HOOK的基本概念源于操作系统或应用程序中的“钩子”,它允许开发者插入自定义代码来拦截、修改或扩展特定事件的处理过程。以下是对HOOK基础知识的详细解释: 一、什么是HOOK? HOOK可以理解为一种编程技术,它允许...

Global site tag (gtag.js) - Google Analytics