`
jxb8901
  • 浏览: 166937 次
  • 性别: Icon_minigender_1
  • 来自: shenzhen
社区版块
存档分类
最新评论

用面向对象思维重构过程式代码

阅读更多

用面向对象思维重构过程式代码

一、背景

有一个自动化执行测试案例的程序,需要根据用户输入的参数决定大量案例中的哪些案例需要执行。程序代码如下(为便于理解,这里仅贴出与主题相关的代码):


	//处理指定参数组的情况    [A,B,C]即指执行参数组(A,B,C)或者[A:C]即指执行参数组(A,B,C)
	private void _run(Config config, TestEngine te, TestNotifier fNotifier, TestCase tc ,String paramGroupName) throws Exception{
		boolean isScope = false;
		String[] paramGroupNames = null;
		if(paramGroupName != null && paramGroupName.trim().length() != 0){
			if(paramGroupName.indexOf(",") != -1){
				paramGroupNames = paramGroupName.split("[,]+");
			}else if(paramGroupName.indexOf(":") != -1){
				isScope = true;
				paramGroupNames = paramGroupName.split("[:]+");
			}else {
				paramGroupNames = new String[]{paramGroupName};
			}
		}
		
		boolean isRun = false;
		
		for (TestParamGroup tpg : tc.getParams()) {
			if(paramGroupNames == null){
				run(config, te, fNotifier, tc, tpg);
				//如果指定的具体生成那个参数组,则只执行该参数组对应的案例
			}else{
				//如果是定义的是范围 A:C
				if(isScope){
					//如果已经超出范围,直接跳出循环
					if(isRun){
						run(config, te, fNotifier, tc, tpg);
						if(paramGroupNames[1].equals(tpg.getName())) break;
					}else if(paramGroupNames[0].equals(tpg.getName())){
						run(config, te, fNotifier, tc, tpg);
						isRun = true;
					}
				}else{
					for(String paramName : paramGroupNames)
						if(paramName.trim().equals(tpg.getName().trim())){
							run(config, te, fNotifier, tc, tpg);
							break;
						}
				}
			}
		}
	}

代码比较长,我简单解释一下:

  • _run方法接受参数Config、TestEngine、TestNotifier、TestCase四个对象实现指定案例的执行、结果通知等功能;
  • 参数paramGroupName就是用户从命令行指定的用于过滤案例的条件。该参数的格式支持A,C,E,F和C:F两种格式(即以逗号分隔的列表和以冒号分隔的范围)
  • 整个方法的逻辑分为两段:以boolean isRun = false;为界,前半段代码解析用户输入的参数;后半段代码则判断并仅运行满足条件的案例。

二、问题

上面的代码有什么问题吗?

没有问题!确实没有问题,那段代码在我们发布的产品中使用了大半年,没有出现任何问题!

能把这篇文章看到这里的同学已经非常难得了,不过我想问一个问题:有多少人真的看了上面的代码并且看懂了?我想一定非常少吧!

不要自责,我自己看那段代码也花了好几分钟,前前后后分析了几遍才真正搞懂。而这正是这段代码的问题所在:方法过大、参数解析逻辑和过滤逻辑混杂不清。当我们需要增加新功能或修改需求的时候,这样的代码带来的隐性成本其实是不可忽视的!

除了上面的问题之外,这段代码的可维护性也是大问题。设想,如果我想要支持A,B,C:D的匹配(即列表和范围混合)呢?如果我想要支持C:或:D(即不设上限或下限)的范围匹配需求呢?任何一种需求的变更,都会立即导致上面的代码结构变得更加复杂深奥难于理解。

三、方案

下面就动用我们的“重构手术刀”将上面的代码整理一下吧。重构的总体思路如下:

  • 将该方法中的参数解析逻辑分离出来;
  • 将判断逻辑与案例遍历逻辑分离出来;

上面的总体思路没有问题:第一步是针对问题对症下药;第二步是让这个药好吃一点,同时为以后的良好体质打下基础。

但具体如何实施呢?直接大刀阔斧实施代码大挪移,将一个方法拆分为三个方法,可以吗?当然可以,但最后出来的代码又会有新的问题:三个方法间代码逻辑藕合度高,可理解性及可维护性并不见得比原来的强多少。(具体改造的代码我就不贴出来了,大家可以自行练习比较)

四、实施

下面是我们运用对象思维重构上面那段代码的结果。

	//处理指定参数组的情况    [A,B,C]即指执行参数组(A,B,C)或者[A:C]即指执行参数组(A,B,C)
	private static interface ParamMatcher {
		public boolean match(TestParamGroup tpg);
	}

核心对象是ParamMatcher,参数匹配器,用于实现案例参数匹配逻辑。设计为接口interface而不是类class的原因是,我们会有多种参数匹配逻辑。如需求中的列表匹配逻辑可用如下代码实现:

	//如果指定的具体生成那个参数组,则只执行该参数组对应的案例
	private static class ListParamMatcher implements ParamMatcher {
		final private Set<String> paramNames;
		private ListParamMatcher(Set<String> paramNames) {
			this.paramNames = paramNames;
		}
		@Override public boolean match(TestParamGroup tpg) {
			return paramNames.contains(tpg.getName().trim());
		}
	}

需求中的范围匹配可用下面的代码实现:

	//如果是定义的是范围 A:C
	private static class ScopeParamMatcher implements ParamMatcher {
		final private String beginParamName, endParamEnd;
		private ScopeParamMatcher(String beginParamName, String endParamEnd) {
			this.beginParamName = beginParamName;
			this.endParamEnd = endParamEnd;
		}
		@Override public boolean match(TestParamGroup tpg) {
			if (beginParamName != null && beginParamName.compareTo(tpg.getName()) > 0)
				return false;
			if (endParamEnd != null && endParamEnd.compareTo(tpg.getName()) < 0)
				return false;
			return true;
		}
	}

上面的代码其实已经实现了不设上限或不设下限的情况。

当然,我们还有匹配所有的逻辑实现:

	private static class AllParamMatcher implements ParamMatcher {
		@Override public boolean match(TestParamGroup tpg) {
			return true;
		}
	}

根据以后匹配逻辑的增加,我们还可以增加相应的实现。

下面来看参数解析逻辑代码分离,重构后的代码如下:

	private static ParamMatcher parse(String paramGroupName) {
		if (paramGroupName != null && paramGroupName.trim().length() != 0){
			if(paramGroupName.indexOf(",") != -1) {
				String[] paramGroupNames = paramGroupName.split("[,\\s]+");
				return new ListParamMatcher(new HashSet<String>(Arrays.asList(paramGroupNames)));
			} else if (paramGroupName.indexOf(":") != -1) {
				int index = paramGroupName.indexOf(":");
				String begin = paramGroupName.substring(0, index);
				String end = paramGroupName.substring(index+1);
				return new ScopeParamMatcher(begin, end);
			} else {
				return new ListParamMatcher(new HashSet<String>(Arrays.asList(paramGroupName)));
			}
		}
		else {
			return new AllParamMatcher();
		}
	}

上面的代码就是先前代码中的前半段代码拷贝,但其性质变成了负责创建ParamMatcher的工厂方法。而我们重构的对象现在变成什么样子了?看代码:

	//处理指定参数组的情况    [A,B,C]即指执行参数组(A,B,C)或者[A:C]即指执行参数组(A,B,C)
	private int _run(Config config, TestEngine te, TestNotifier fNotifier, TestCase tc, ParamMatcher pm) throws Exception{
		int count = 0;
		for (TestParamGroup tpg : tc.getParams()) {
			if (pm.match(tpg)) {
				count += run(config, te, fNotifier, tc, tpg);
			}
		}
		return count;
	}

只剩下了案例循环逻辑和执行逻辑,代码非常简洁,一目了然。

五、总结

我们前面通过一个实际的案例展示了如何运用面向对象的思想重构一段过程化的代码,对象化的代码和过程化的代码两者在实现的逻辑功能上是完全一致的,但面向对象方式通过职责的分离和再分配,实现了逻辑的解藉,最终达到了 更好的可理解性和可维护性的目的。

这如同一家公司,当其业务简单单一的时候,不需要划分部门就可以很好地处理业务。随着业务量的增长,我们可以通过对象的方式将职责分配给不同的部门,通过部门间的协同共同实现原有的业务逻辑。而此基础之上,各部门(对象或模块)就可以相对独立地发展(维护),更好地支持业务(功能)的拓展。

 

分享到:
评论

相关推荐

    面向对象的思想

    重载和隐藏是面向对象中的两个重要概念,重载允许在同一作用域内使用相同的函数名但有不同的参数列表,隐藏则是在子类中使用与父类相同的方法名但实现不同功能。 在理解和掌握面向对象编程的过程中,学习如何有效地...

    简明代码和代码重构思维导图

    - **SOLID原则**:单一职责原则、开闭原则、里氏替换原则、接口隔离原则和依赖倒置原则,是面向对象设计的基本指导原则。 - **设计模式**:工厂模式、单例模式、观察者模式等,是解决常见设计问题的成熟解决方案。...

    [免费PDF高清]重构-改善既有代码的设计+中文版. 侯捷.熊节.rar

    在C#等面向对象编程语言的背景下,重构是程序员日常工作中不可或缺的技能。 在C#编程中,重构是提升代码质量的关键步骤。它可以帮助开发者在面对日益复杂和庞大的代码库时,保持代码的清晰和简洁,降低维护成本。书...

    面向对象分析与设计

    11. **重构**:在面向对象设计过程中,随着系统的发展,可能需要对代码进行重构,以改善其结构和性能,而不改变其外在行为。 12. **测试驱动开发(TDD)** 和 **行为驱动开发(BDD)**:这两种开发方法鼓励先编写测试...

    深入浅出面向对象分析与设计(中文版)

    面向对象分析与设计是软件开发过程中的核心环节,它涵盖了如何使用面向对象的思维方式来理解和构建复杂的系统。《深入浅出面向对象分析与设计(中文版)》这本书旨在帮助程序员深入理解这一领域,不论你是Java还是...

    哈尔滨工业大学-面向对象技术

    8. **重构**:面向对象编程鼓励持续改进代码结构,保持代码的清晰和简洁,这就是重构。通过重构,可以提升代码质量,减少错误,同时保持系统的可维护性。 9. **异常处理**:面向对象编程中的异常处理机制允许程序...

    面向对象分析与设计(第3版)

    9. **重构**:重构是改善现有代码结构的过程,保持代码可读性和维护性,而不会改变其外在行为。 10. **案例研究**:书中可能会通过实际项目案例,帮助读者将理论知识应用于实践,提升理解和应用能力。 《面向对象...

    代码重构(C# & ASP.NET版),中文完整扫描版

    7. 面向对象编程理论:书中将介绍面向对象编程的理论基础,并展示如何在重构实践中应用这些理论。 8. C# 3.0及其后的新特性:本书将介绍C# 3.0版本引入的一些新特性,比如LINQ,以及如何利用这些特性来提升重构效果...

    深入浅出面向对象分析与设计中文版

    本资源“深入浅出面向对象分析与设计(中文版)”涵盖了这些关键概念,通过实例解析和清晰的讲解,帮助初学者建立起面向对象思维,掌握分析和设计的技巧。阅读此书,你将能够更好地理解和运用面向对象编程,为软件开发...

    代码重构-以贪吃蛇为示例(四)-继续封装

    通过面向对象的设计,可以更好地模拟现实世界中的实体,使代码更符合人类思维。 5. **错误处理**:重构的过程中,可能会增加适当的错误处理机制,如异常处理,来增强程序的健壮性,确保在遇到异常情况时能优雅地...

    戏说面向对象程序设计(C#版).pdf

    - **重构**:通过重构代码,优化程序结构,使其更加符合面向对象的最佳实践。 #### 三、面向对象设计的关键原则 **面向对象设计的原则**: - **封装**:将数据和操作数据的方法绑定在一起,隐藏内部实现细节,只...

    面向对象的系统分析与设计原版课件

    9. **最佳实践**:原版课件通常会涵盖最新的技术趋势和最佳实践,比如敏捷开发、持续集成、代码重构等,这些都将帮助你成为一个优秀的面向对象开发者。 通过深入学习《面向对象的系统分析与设计》第二版的课件,你...

    java面向对象游戏

    在编程世界中,Java是一种广泛使用的面向对象编程(OOP)语言,因其强大的功能和跨平台性而备受青睐。在这个“java面向对象游戏”的项目中,我们看到开发者使用了Java OOP思想来实现一个猜拳游戏,这为初学者提供了...

    重构-改善既有代码的设计

    在Java编程环境中,重构尤其关键,因为Java是一种广泛应用的面向对象语言,其设计模式和重构技术的运用对于代码的整洁和高效至关重要。书中的许多重构技巧不仅适用于Java,也适用于其他面向对象语言,比如C#、Python...

    面向对象程序设计(21世纪全国应用型本科计算机系列实用规划教材)

    在21世纪全国应用型本科计算机系列实用规划教材中,面向对象程序设计作为一门重要的课程,旨在培养学生的系统思维和抽象能力,以适应实际项目开发的需求。 在OOP中,有四个核心概念:封装、继承、多态和抽象。封装...

Global site tag (gtag.js) - Google Analytics