论设计模式在软件开发中的应用
作者:jercy 时间:2008-11-2
在解决这个论题之前,我们首先要了解设计模式的概念,及其基本的分类。
“设计模式”这四个字,相信大家在很多地方都会看到,什么是设计模式呢? 一个设计模式提供一种提炼子系统或软件系统中的组件的,或者它们之间的关系的纲要设计。设计模式描述普遍存在的在相互通讯的组件中重复出现的结构,这种结构解决在一定的背景中的具有一般性的设计问题。
由于[GOF95]是论述软件模式的著作的第一本,因此有些人常常使用设计模式(Design Pattern)一词来指所有直接处理软件的架构、设计、程序实现的任何种类的模式。
Intorduction
设计模式常常划分成不同的种类,常见的种类有:
创建型设计模式,如工厂方法(Factory Method)模式、抽象工厂(Abstract Factory)模式、原型(Prototype)模式、单例(Singleton)模式,建造(Builder)模式等
结构型设计模式,如合成(Composite)模式、装饰(Decorator)模式、代理(Proxy)模式、享元(Flyweight)模式、门面(Facade)模式、桥梁(Bridge)模式等
行为型模式,如模版方法(Template Method)模式、观察者(Observer)模式、迭代子(Iterator)模式、责任链(Chain of Responsibility)模式、备忘录(Memento)模式、命令(Command)模式、状态(State)模式、访问者(Visitor)模式等等。
以上是三种经典类型,实际上还有很多其他的类型,比如Fundamental型、Partition型,Relation型等等。
设计模式是面向对象编程的热门话题之一,越来越多的开发人员认识到设计模式的重要性。采用各种语言实现设计模式的文章也越来越多,但是很多开发人员发现很难将设计模式与实际开发中需要解决的具体问题相联系。因为使用设计模式的难点往往不在于模式的实现,而在于很难确定哪种模式可以在现实的应用场景中采用,从而导致了在现实的项目中,面对客户的压力,我们总是采用最直截了当的方法解决问题,来不及多考虑这些方法的优劣,即使明知将带来更大的麻烦也必须如此。有些时候因为选择了不恰当的设计模式,使原本简单的问题变得复杂化。
这里通过先通过几个简单例子的说明来介绍在设计模式实际的软件开发中的使用。然后再通过一个项目的分析来论述软件开发中设计模式的使用效果。
Applcation
让我们来看几段常见但又不够优雅的代码,这些代码 “你”、“我”、“他”或许都曾写过,但是终有一天你也会与我一样,看着有想撞墙的感觉(如果你写了N久这样的代码,还没有这种感觉,八成你是对编程不再感兴趣了,你对其已经麻木):
1.过多的if…else判断
if (type == 1) {
//调用获取信息方法1
} else if (type == 2) {
//调用获取信息方法2
…….
} else {
//调用获取信息方法7
}
这是我们在编程或者看书中要遇到的一段代码,如果条件判断非常之长,并且其他有些地方也有根据类型来做不同处理的情况。这些代码对于后阶段的维护简直是一场噩梦。
2.多次载入资源(例如配置文件的读取),引起资源损耗
public static String getProperty(String propKey) throws Exception ...{
Properties prop = new Properties();
InputStream propConfFile = Util.class.getClassLoader()
.getResourceAsStream("configure.properties");
//载入propConfFile到prop中,从prop中获取propKey的值,并将其返回
}
该段代码是我以前在一个项目中写的一段代码,该段代码用于读取工程配置文件的属性,但该段代码是存在一些问题的,因为在每次获取属性时,它都重新载入资源,造成了资源的过多损耗。
在我编码的过程中,遇到的问题还有很多。不够优雅的代码、过于僵硬的设计,等等,下面我将通过如上两个例子讨论-----
1. 解决过多的if…else判断问题
如果在一段代码中,不少地方需根据某类型或状态等做出不同的处理,那当类型或状态增加时,这些代码将会过于僵硬,扩展性差,只有在各个分布了if…else的再增加一个else if,可维护性可想而知。设计模式中有一种模式可以解决该问题,即状态模式。状态模式给我们带来的好处如下:
1) 状态模式需要对每一个对每一个系统可能取得的状态创立一个状态类(State)的子类,当系统的状态变化时,系统改变所选的子类。与一个特定的状态有关的行为都被包装在一个特定的对象里,而且当需要增加新的状态时,可以以子类的方式将它加到系统里,从而提高了易维护性和可扩展性;
2)由于每一个状态都被包装到了类里面,避免了使用过多的条件转移语句。
下面我们对该例进行演示性的改进。我们可以定义一个类型接口,该类相当于状态模式中的状态类。
public interface Type {
/**
* 获取信息
*/
public Object getInfo();
/**
* 获取结果
*/
public Object getResult();
}
类型1、类型2等可以实现该接口,代码略:
2. 解决过度资源损耗问题
在该例中,每次通过getProperty(…)方法获取某属性时,都会重新载入文件中的所有内容,造成资源的不必要损耗。该设计模式中,对于此种情况,可以通过单例(Singleton)模式来优化处理。
import //略
public class PropertiesUtil ...{
private static Map propertiesMap = null;
public static String getProperty(String propKey) throws Exception ...{
if (propertiesMap == null) ...{
//当propertiesMap为空时,载入文件,将其键值对放入propertiesMap中(略)
}
//在propertiesMap中获得propKey属性,并将值返回(略)
}
}
可以考虑实现单例模式的地方还有很多,例如:
1)对于计算机的外部资源打印机的情况,因只有一个Printer Spooler,为避免两个打印作业同时输出到打印机中,可考虑用单例模式实现。
2)Window的回收站在整个系统中只有唯一的一个实例,而且回收站自行提供自己的实例,回收站也是单例模式的应用
3 BookManageSystem
下面通过我以前做过的一个《图书管理系统》中设计的一个问题来详细做阐述:
我的系统的按功能包括:登录模块,图书借阅模块,图书信息查询模块,归还图书模块,用户管理模块,图书管理模块等。
而在这些模块中又包含很多小的模块,比如:用户管理模块又包含:添加新用户,删除用户,更改用户信息三个子模块,类似的还有其他模块。
那我们来如何处理这种复杂的系统呢?按照我们平时的习惯:处理复杂系统的一个常见的方法便是将其“分而治之“,把一个系统划分为几个较小的子系统。但是这样做了以后,设计者仍然会发现一个子系统内仍然有太多的类型要处理。而使用一个子系统的使用端往往只会关注一些特定的功能,却要同时与子系统内容内部的许多对象打交道后才能达到目的。
借书 |
还书 |
查询 |
借阅者 |
借阅者 |
借阅者 |
这就是一种不便,它使得系统的逻辑变得不必要的复杂,维护成本提高,复用率降低。
一开始结构就是这样设计的并且代码也是这样编写的,后来上网看到了一个类似的系统(医疗系统),按照部门职能,这个系统可以划分为挂号、门诊、划价、化验、收银、取药等。看病的病人要与这些部门打交道,就如同一个子系统的使用端与一个子系统的各个类型打交道一样,不是一件容易的事情。有朋友在帖子下面提示:解决这种不便的方法便是引进Facade模式。仍然通过医院的范例说明,可以设置一个接待员的位置,由接待员负责代为挂号、划价、缴费、取药等。这个接待员就是Facade模式的体现,病人只接触接待员,由接待员负责与医院的各个部门打交道。
我一想,自己不傻了吗,而对于《图书管理系统》我可以建立一个图书馆员来处理所有读者对系统的要求,这样就像医院的接待员一样,Facade模式的门面类型将使用端与子系统的内部复杂性分隔开,使得使用端只需要与门面对象打交道,而不需要与子系统内部的很多对象打交道。
Facade模式要求一个子系统的外部与其内部的通讯必须通过一个统一的门面(Facade)对象进行。Facade模式提供一个高等级的接口,使得子系统更易于使用。
Summary
总结:
在使用了设计模式后,明显的发现以下几点::
1) 可以比较好的分工(比如,使用接口类型模式)
2) 代码组织更有条理(b比如buiilder模式:像查询的结果,中间的产生过程是非常复杂的,如果不用builder模式,谁去改了,也许过段时间,他自己都忘记了)
但是:切记切记:千万不能为了模式而模式。重要的是各种模式中的思想,当你理解了思想之后,在实际的开发中不用想着硬套,自己就会想到使用(就算你已经忘记它是什么模式)。