`
fangang
  • 浏览: 876826 次
  • 性别: 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
社区版块
存档分类
最新评论

软件可以这样功能扩展

阅读更多
在所有关于软件维护的故事中,功能的扩展是一个永恒的话题。正因为软件系统需要功能的扩展,需要新功能的加入,才使我们的编程需要那么多的设计。可以说,正是因为新功能的扩展,使得原有的系统质量下降;正是因为软件质量的下降,才使我们需要进行深入的分析与研究,制订设计原则,总结设计模式;正是因为要解决软件质量下降的问题,经过一番艰苦卓绝的摸索过程,我们才认识到系统重构才是解决该问题的最佳方案。

然而,事情总是这样的,每个系统当我们进行初次的设计时,设计思路、程序结构总是比较完美的。可是当初次设计结束后,我们在日后的维护中,开始往系统里添加新功能时,系统开始不完美了,甚至开始出现问题了,新增的功能总是或多或少有些水土不服。怎么办呢?要保证每次需求的变更时软件质量不会下降,必须记住这样一个原则:先重构再添加新功能。

添加新功能前先重构原有系统,其目的有两个:
1. 软件的设计总是与软件的复杂程度有关的,原有的设计是在原有需求不复杂的条件下做出的,但随着新功能的加入,软件复杂度在发生着变化,因此必须要调整原有的设计以适应新的需求;
2. 为了提高软件的可维护性与易变更性,添加新功能应遵循OCP原则。而要遵循OCP原则,我们应当在不添加新功能的前提下先进行重构,设计出可扩展点出来,然后再添加新功能。

是的,明白这两点非常重要。软件维护越来越困难,不是因为客户提出了需求变更,而是因为我们没有随着软件复杂度的增加改变我们的软件结构。软件需求起初比较简单,是几乎所有软件的共性。但随着软件复杂度的增加,我们却不敢有效地调整现有的软件结构,以适应新的需求,这就真正是我们的问题了。不敢调整,是害怕原有功能会出错,但不调整,则意味着我们软件设计的问题会越来越大,进而越来越难于维护。改与不改,我们面临着两难的抉择。

解决这个两难的难题其实不难,实际上就是一层窗户纸一桶就破了。试想,我们不敢修改原有代码的真正原因是因为害怕改出问题影响原有功能的正常运行。那么,我们找一个方法使我们在修改原有代码时不会出现问题,换句话说是出现问题以后会及时发现,则问题就可以解决,这个方法就是重构与测试。

客户判断一个功能是否正常运行的标准,就是当输入一个值后,能得到客户期望的结果,不管系统内部是怎样运行的。因此,建立这样一个测试用例,让软件系统在重构前后都能通过这些测试用例,就可以保证重构的正确性(关于如何建立,我们还会在后面仔细讨论)。重构以后,外部功能是一致的,但内部程序结构却变得更加易于添加新的功能,使新的功能与原有系统可以有机地融为一体,这才是我们的目的。说起来比较抽象,我们来举一个示例吧:

在许多系统中,只要有报表出现就有需求要实现Excel数据导出功能。在一个系统中,客户起初提出的需求是实现“全部导出”、“按选择导出”、“导出本页”。为此,我们设计了一个单选框,并在后台程序中编写了一个if语句,如果选择的是“全部导出”,则查询所有记录并导出;如果选择的是“按选择导出”,则从前端获得一个主键列表,即用户已选择的行,以此作为条件查询导出;如果选择的是“导出本页”,则查询本页数据并导出。
	String exportTypeName = (String)params.get("exportType");
	if("exportAll".equals(exportTypeName)) {
		//全部导出的代码
	} else if("exportChoosen".equals(exportTypeName)) {
		//按选择导出的代码
	} else if("exportOnePage".equals(exportTypeName)) {
		//导出本页的代码
	}

这样的设计没有问题,也是大多数人首先想到的设计。但是,多个不同的选择放在一个类中必将为功能的扩展带来麻烦。随后,客户提出了新的需求,按页导出,即根据客户的要求,从第几页到第几页进行导出。按照前面的设计,我们必然是在原有基础上再增加一个if语句,实现按页导出。
	String exportTypeName = (String)params.get("exportType");
	if("exportAll".equals(exportTypeName)) {
		//全部导出的代码
	} else if("exportChoosen".equals(exportTypeName)) {
		//按选择导出的代码
	} else if("exportOnePage".equals(exportTypeName)) {
		//导出本页的代码
	} else if("exportPageRange".equals(exportTypeName)) {
		//按页导出的代码
	}

但这样的设计违反了OCP原则,也是大多数系统代码质量下降的重要原因之一。在一些文章中,if语句被称为“罪恶之源”,因为大量使用if语句将会大大降低系统的可读性、可维护性与易变更性,使系统难于维护。因为不断添加的if语句很快会使代码由数百行膨胀到几千行,还会大量掺杂各种重复代码与糟糕设计。其根本原因就在于,它让一个类承载了过多的职责,降低了功能内聚而提高了功能耦合。它不仅加大了我们修改代码的难度,也将加大我们测试代码的成本,因为任何一项修改都必须要对所有功能进行测试。

因此,我们需要调整我们的代码结构,改变我们的设计(到这里也许你开始理解我所说的改变代码结构以适应新的需求的含义了吧)。我们说扩展新功能的设计应当符合OCP原则。怎样的设计才是符合OCP原则的呢?首先可以想到的是,让“按页导出”这个功能的代码放到另一个类中,而不写在原有类中。比如,我们可以创建一个新类ExportPageRange,通过接口Exporter接入到原类ExportBus,让ExportBus调用其相应的方法:



但是,这样的设计我们依然需要修改原类ExportBus,在if语句中调用接口:
	String exportTypeName = (String)params.get("exportType");
	If ("exportAll".equals(exportTypeName)) {
		//全部导出的代码
	} else if ("exportChoosen".equals(exportTypeName)) {
		//按选择导出的代码
	} else if ("exportOnePage".equals(exportTypeName)) {
		//导出本页的代码
	} else if ("exportPageRange".equals(exportTypeName)) {
		//按页导出的代码
		Exporter exporter = new ExportPageRange();
		exporter.doExport(resultset);
		return exporter.getFileInfo();
	}

加粗部分是我们不得不在原类中添加的代码。如果不使用这个if语句而让Exporter接口的实现类与判断条件建立一种联系,则问题可以得到解决。要实现这种联系有很多方法,其中一个方法就是建立配置文件,让配置文件中的名称与实现类关联起来就可以了,为此我们需要这样设计:



然后进行这样的配置:
<bean id="exportBus" class="com...reporter.bus.impl.ExportBusImpl">
	<description>导出数据BUS</description>
	<property name="exportTypes">
		<map>
			<entry key="exportAll"><!-- 全部导出 -->
				<bean class="com...reporter.export.ExportAll"/>
			</entry>
			<entry key="exportOnePage"><!-- 导出本页 -->
				<bean class="com...reporter.export.ExportOnePage"/>
			</entry>
			<entry key="exportChosen"><!-- 按选择导出 -->
				<bean class="com...reporter.export.ExportChosen"/>
			</entry>
			<entry key="exportPageRange"><!-- 按页导出 -->
				<bean class="com...reporter.export.ExportPageRange"/>
			</entry>
		</map>
	</property>
</bean>


这样,配置文件中的entrykey就与导出程序的实现类建立了联系,因此在ExportBus中原来的那个if语句就演变成了这样:
	String exportTypeName = (String)params.get("exportType");
	Exporter exporter = exportTypes.get(exportTypeName);
	exporter.doExport(resultset);
	return exporter.getFileInfo();

加粗的部分实质性替代了原来那个if语句。这样的设计,让各个不同类型的导出程序得到有效解耦,然后通过接口与配置文件实现动态地装配。这就是一种典型的可扩展点设计,当我们还有新的导出类型的功能需要扩展的时候,不需要修改原有的任何代码,而只需添加一个Exporter接口新的实现类,再进行相应的配置,功能就可以实现。这样的设计是可以满足OCP原则的,而在系统中实现这种可扩展性设计的功能点,我们就称之为“可扩展点”。

以上的设计是我们最终应当实现的设计,是结果。但要达到这样的设计,即分析整个设计的过程,我们真的没有修改原程序吗?不,我们修改了。那么这怎么叫符合OCP原则呢?问题十分犀利哈。转了那么大一圈,现在才是我要真正提出我的观点的时候了。我认为,要改善遗留系统的可维护性,要遵守OCP原则,并不是意味着实现新需求时不能修改原有代码。要遵从“两顶帽子”的设计原则,先重构原有的代码,使其具有可扩展功能,然后再添加新程序,使其满足OCP原则,这才是可扩展设计的关键之所在。

具体来说,就是第一步修改代码,第二步添加功能。第一步,修改原有代码,在保证原有功能不变的前提下,设计出可扩展点,使其在以后添加新功能时不必修改原有代码。在本例中就是将原有的三种导出方式从原有代码中抽取出来,形成Exporter接口与ExportAll、ExportOnePage、ExportChosen三个实现类,以及它们的配置文件。这样,Exporter接口就是数据导出方式的可扩展点。这时,由于没有添加任何新功能,我们可以编写测试代码进行测试,或者手工测试。

第二步,就是添加新功能。由于可扩展点已经做出来了,剩下的工作其实就很简单了:编写实现类ExportPageRange,然后配置到系统中,整个设计符合OCP原则。功能扩展就应当这样做,才能使我们的软件在维护中始终能保持高质量的代码。
(续)

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

特别说明:希望网友们在转载本文时,应当注明作者或出处,以示对作者的尊重,谢谢!
  • 大小: 6.3 KB
  • 大小: 12.5 KB
分享到:
评论
2 楼 fangang 2014-05-22  
o624366705 写道
对我很有帮助,谢谢博主的分享!xml就相当于一个bean工厂,程序根据传入的param获取相应的实例,我可以这样理解吗?

,是滴
1 楼 o624366705 2014-05-21  
对我很有帮助,谢谢博主的分享!xml就相当于一个bean工厂,程序根据传入的param获取相应的实例,我可以这样理解吗?

相关推荐

    易语言调试功能扩展

    本文将深入探讨“易语言调试功能扩展”这一主题,这是一份源码资源,旨在增强易语言程序的调试能力,帮助开发者更好地理解和优化他们的代码。 调试是软件开发过程中的关键环节,它允许程序员定位并修复代码中的错误...

    工业机器人示教系统扩展功能与软件开发.pdf

    在研究工业机器人示教系统时,需要关注其扩展功能和软件开发的各个方面。首先,示教系统是工业机器人技术中一个十分重要的部分,它决定了机器人是否能够准确无误地完成特定的任务。工业机器人的示教方式主要有在线示...

    无线网卡 上网拨号软件 扩展短信功能

    在IT领域,无线网卡、上网拨号软件以及扩展短信功能是网络通信和移动设备连接互联网的重要组成部分。这里,我们将详细探讨这...无论是个人用户还是企业用户,都可以通过这样的软件实现快速、稳定的网络接入和通信需求。

    Arduino 9合一多功能扩展板使用

    这是一套arduino 9合一扩展板的使用说明,文档内将扩展板常用功能简单的描述了引脚,并用实例代码做了演示

    右键扩展功能

    "右键扩展功能"是指通过特定的程序或工具来增强和自定义Windows资源管理器的右键菜单,使其包含更多的选项和功能,以满足用户个性化的需求。这种扩展通常涉及注册表编辑、第三方软件安装或者使用外壳扩展程序。 ...

    系统右键扩展功能小工具

    《系统右键扩展功能小工具详解》 在日常的计算机操作中,我们常常需要查看或隐藏系统的文件和文件扩展名,这些操作往往需要通过“文件夹选项”进行繁琐的设置。为了解决这一问题,出现了名为“系统右键扩展功能”的...

    一款实用的电脑右键扩展软件

    该软件的核心功能在于其右键菜单的扩展性。它允许用户根据个人工作习惯和需求,添加或移除特定的右键菜单项。例如,你可以将常用的文件压缩、解压、转换格式等操作添加到右键菜单,从而节省了打开应用程序再执行相应...

    C语言扩展IMAGINE功能

    IMAGINE开发者工具包(CToolkit)为有经验的C/C++程序员提供了丰富的库和文档,允许用户进行软件商业版本的修改或者开发全新的应用程序以扩展软件功能。在EML(EM Software)中,用户可以利用“ImageMetadata”显示...

    最新单片机仿真 独立式键盘的按键功能扩展:以一当四

    最新单片机仿真 独立式键盘的按键功能扩展:以一当四最新单片机仿真 独立式键盘的按键功能扩展:以一当四最新单片机仿真 独立式键盘的按键功能扩展:以一当四最新单片机仿真 独立式键盘的按键功能扩展:以一当四最新...

    串口扩展卡的驱动软件

    串口扩展卡驱动软件是计算机硬件系统中用于管理和控制串行接口扩展卡的软件组件,它使得操作系统能够识别并有效利用这些硬件资源。在本文中,我们将深入探讨串口扩展卡的基本概念,串口扩展卡驱动软件的工作原理,...

    右键扩展功能插件 (显示隐藏系统文件+扩展名).

    首先,右键扩展功能插件是一种工具,它可以为Windows的右键菜单增加自定义的功能选项。这些插件通常由第三方开发者创建,以满足用户个性化和专业化的操作需求。例如,本插件专门设计用于显示通常被系统隐藏的文件和...

    鼠标映射功能扩展

    标题中的“鼠标映射功能扩展”指的是通过软件技术来增强鼠标的功能,特别是扩展鼠标按键的用途,使其不再局限于默认的左键、右键和滚轮操作。这种技术允许用户自定义鼠标各个按键的行为,比如将侧键设定为特定的...

    右键扩展功能插件 (显示隐藏系统文件+扩展名)

    安装并使用“右键扩展功能插件 (显示隐藏系统文件+扩展名)”后,用户可以直接在文件或文件夹上右键点击,选择相应选项,而无需通过控制面板或文件资源管理器的高级设置进行繁琐的设置。这对于经常需要处理各种文件...

    扩展功能一支持库

    在IT领域,扩展功能支持库通常指的是为了增强软件或操作系统的核心功能而开发的一系列外部组件或模块。这些库提供了额外的API(应用程序编程接口),使得开发者可以利用它们来实现更复杂的功能,或者优化已有功能的...

    软件工程与软件系统可扩展性评估.pptx

    ### 软件工程与软件系统可扩展性评估 #### 第一章 软件工程概述 **软件工程定义:** 软件工程是一种将系统化、规范化、可度量化的方法应用于软件的开发、运行和维护过程,并对这种方法进行研究的学科。它强调软件...

    右键扩展功能+(显示隐藏系统文件+扩展名).rar

    "右键扩展功能+(显示隐藏系统文件+扩展名)"这个压缩包文件显然是为了增强Windows系统的右键菜单功能,特别是针对显示隐藏文件和显示文件扩展名这两个常见需求。 首先,我们来详细了解一下“显示隐藏系统文件”的...

    软件可扩展性实践PPT课件.ppt

    这意味着当需求变化时,我们可以通过扩展模块来增加新功能,而不是直接修改原有代码,这样可以保持代码的稳定性,降低维护成本。 实现OCP的方式有很多,例如使用抽象基类和纯虚函数来实现接口,允许在不修改原有类...

    软件功能说明书模板

    ### 软件功能说明书模板解析 #### 一、概述 **1.1 目的** 本软件功能说明书旨在提供一套全面且详细的指导方案,帮助软件开发团队理解系统的各项功能需求,确保软件的设计与实现能够满足预期的目标。通过标准化...

    用C++ Builder简便扩展Intouch组态软件.pdf

    通过OPC 223协议可以与Intouch进行实时数据交换,从而实现对组态软件功能的增强。 文章还讨论了223协议的实现原理,223协议是基于消息机制的,它允许两个应用程序通过相互传递消息来请求、应答和传输数据。这一过程...

    软件可扩展性实践.ppt

    这意味着当需求变化时,我们可以通过添加新的代码来扩展软件功能,而不是修改现有的代码。这有助于保持代码的稳定性,降低维护成本,并提高软件的可靠性。 实现OCP有多种方式,例如: 1. **抽象基类与纯虚函数**:...

Global site tag (gtag.js) - Google Analytics