软件项目实训及课程设计指导——如何应用策略设计模式分离JDBC数据库连接中的外部环境信息
1、什么是策略(Strategy)设计模式
策略设计模式把"算法"(也就是软件应用系统中的业务规则或者待实现的功能等)和"环境"(封装软件应用系统在实际应用时的场景)相互分离,其中的"环境"程序类主要是负责维护和查询"算法"程序类,而各种"算法"则由具体的策略程序类加以封装和实现,并且策略程序类可以通过面向对象设计方法中的继承机制来产生出层次性的策略程序类,可以根据应用的需要进行替换。
策略设计模式在GOF设计模式的分类中属于GOF设计模式中的行为型类型的设计模式。如下示图为体现策略设计模式中的各个类之间关系的UML类图,依据此UML类图,可以了解到策略设计模式提供了一种替代面向对象设计方法中的继承的方法,将继承改变为组合,而且既保持了继承的优点(实现代码重用)但又比继承更具有灵活性("算法"独立,可以任意地扩展)。
2、为什么要在软件应用系统编程实现中应用策略设计模式
(1)策略设计模式能够把"算法"和"环境"分离开
在软件应用系统的程序功能代码编程实现中,开发人员经常会应用策略设计模式,以达到把实现"算法"的程序代码本身与使用算法的"客户"端程序之间的耦合关系相互分离。也就是策略设计模式能够让"算法"独立于使用它的客户程序而独立地变化、并且"算法"自身的变化(比如"算法"的增减、修改等)不会影响到使用"算法"的客户端程序。
因此,应用策略设计模式后,在软件应用系统设计方面所能够达到的设计目标:
1)保证软件应用系统具有良好的可扩展性和可维护性;
2)使用策略设计模式可以在系统的需求发生变化时,对程序代码的修改量较少,并且能够快速地适应变化(通过添加新的功能实现类)。
(2)策略设计模式的应用场景示例一
比如,在构建软件应用系统持久层数据库连接Connection接口的对象实例时不希望出现下面的状况:
将实现各种不同物理数据库系统的"连接"的功能代码(在这里代表策略模式中的"算法")直接出现在使用数据库连接对象实例的"程序"中(也就是策略模式中的"客户"——如软件应用系统持久层中的各种数据库访问操作的DAO组件)。
这样的设计方案将使得"客户"相关的程序代码和具体的"算法"功能实现的程序代码完全绑定,而且由于"算法"相关的程序也会经常发生变化。应用这样的设计方案的后果将会导致程序代码的可维护性比较差——因为一旦"算法"相关的程序代码发生了改变,将会影响到相关的各个客户程序也都需要被动地修改和完善。
(3)策略设计模式的应用场景示例二
作者再应用生活中的示例为读者说明策略设计模式中的"算法"和"环境"相互分离的作用效果—— "汉堡包"为什么在全世界的口味是一样的呢?
因为"汉堡包" 制作的工艺("算法")是独立"环境"的(不同的国家或者地区);而"汉堡包"的客户(不同国家的人)在不同的环境中都能够获得一致"口味"的汉堡包。
3、"J2EE项目实训——UML及设计模式"一书详细介绍了策略设计模式的具体实现
作者考虑到本文的篇幅关系,无法详细地为读者介绍GOF设计模式中的策略设计模式更深入的知识及具体实现程序代码示例等内容。因此,有关策略设计模式的具体编程实现及程序代码示例,请读者参考清华大学出版社出版的作者的"J2EE项目实训——UML及设计模式"一书(如下示图为该书的封面)中的第10章"典型GOF设计模式及应用"中的有关内容。
作者将为读者介绍如何将策略模式设计应用于创建软件应用系统持久层数据库连接Connection接口对象实例的功能实现程序代码中。
4、为什么要应用策略模式创建数据库连接Connection接口类型的对象实例
在JDBC数据库应用编程技术中,一般是利用下面程序代码示例中所示的常规的数据库连接类的功能实现代码完成对数据库连接Connection对象的实例化——常规的数据库连接Connection对象的实例化的功能实现代码示例
public class ConnectDBBean implements ConnectDBInterface { String JDBC_DBDriver_ClassName=" com.mysql.jdbc.Driver "; String JDBC_DSN_URL=" jdbc:mysql://localhost:3306/webbank "; String JDBC_dbUserName="root"; String JDBC_dbUserPassWord="root"; private java.sql.Connection oneConnectionObject = null; public ConnectDBBean() throws WebBankException { try{ Class.forName(JDBC_DBDriver_ClassName); } catch (java.lang.ClassNotFoundException e){ //在此先不考虑异常处理 } try{ oneConnectionObject =DriverManager.getConnection(JDBC_DSN_URL, JDBC_dbUserName, JDBC_dbUserPassWord); } catch (java.sql.SQLException e){ } catch (NullPointerException e){ } }}
作者为了节省篇幅,在上面的程序代码示例中,省略了一些无关的程序代码语句如包的引入和异常的捕获等。并请读者注意其中的黑体标识部分的语句代码,本程序代码示例是连接MySQL数据库系统中的名称为webbank数据库文件,并且访问数据库系统的账户及密码都设置为root。
在常规的数据库连接类的功能实现代码中,所存在的主要问题在于将数据库连接类中所需要的外部环境参数(上面示例代码中黑体部分的参数值)直接写在功能实现的程序代码中,而这些数据库连接相关的信息和参数是经常会发生变化的——比如访问数据库系统的账户所对应的密码、数据源的目标服务器主机的位置(可能为本机、也可能为网络中的远程主机)等。
软件应用系统的程序开发实现人员不应该在比如密码等这样的工作参数发生变化时,也要相应地去修改软件应用系统中的数据库连接实现的功能程序代码。否则,所开发实现的程序太缺少灵活性!
5、应用策略设计模式重构数据库连接Connection对象实例的创建代码
在GOF设计模式中的策略设计模式的基本思想,主要是要求软件应用系统的编程开发实现人员将某功能实现中所需要的各种可变化的环境参数分离出来,并用一个上下文环境程序类进行封装。
在示例项目银行账户信息管理系统中,作者首先是采用Java属性配置文件提供整个系统的配置信息,然后再利用一个名称为ClassNameConfig的程序类封装对Java属性配置文件中的各个属性项目的解析,从而动态地根据应用的需要获得对应的数据库连接相关的工作参数。
名称为ClassNameConfig的程序类也就是本示例中的上下文环境程序类,作者在下文中为读者介绍如何实现ClassNameConfig的程序类。
6、构建策略设计模式中的上下文环境ClassNameConfig类的功能实现代码
(1)在项目中添加一个属性配置文件 classNameConfig.properties
根据Java系统平台中对属性文件(*.properties)解析的基本要求,该属性配置文件应该要存放在软件应用系统的classpath所在的路径中。该路径对于Web方式的应用程序则是在WEB–INFO/classes目录下。
但读者一定要注意属性配置文件(*.properties)的相对路径,否则对属性配置文件进行解析的程序将会由于找不到此属性配置文件而抛出找不到文件的异常错误信息。
因此,读者只需要在MyEclipse开发工具中右击项目的src文件夹,并在弹出的【新建文件】的对话框中输入属性配置文件名 classNameConfig.properties。最后的操作结果的图示请见下图示例图所示。
(2)设计属性配置文件 classNameConfig.properties中的配置项目
由于Java属性配置文件(*.properties)的项目内容是"键/值"对的形式存储,因此根据项目的数据库连接的需要分别提供五个不同的属性项目。
每个项目的内容请见下图所示的内容——它们分别代表MySQL数据库的JDBC驱动程序类、MySQL数据库文件webbank和在该数据库中的账号root和密码root等信息。
(3)设计一个获得属性配置文件中的各个属性项目的ClassNameConfig程序类
在java.lang.Class类中的getResourceAsStream方法将直接返回一个InputStream类型的流对象实例,而getResourceAsStream功能方法是查找具有给定名称的资源并返回 InputStream对象。
但要注意的是:getResourceAsStream功能方法在查找和定位属性配置文件时是相对于"/"根路径下的位置——这样的路径是指定绝对路径;如果在标识属性配置文件时不以"/"字符开头,则查找和定位的路径是相对于这个程序类(也就是ClassNameConfig程序类)文件所在包的路径。比如,下面的程序代码示例:
InputStream oneInputStreamObject =ClassNameConfig.class.getResourceAsStream("/classNameConfig.properties");
而java.lang.ClassLoader类中的getResource()功能方法则是从系统的classpath的根路径开始查找带有给定名称的资源路径和文件名并返回一个URL类型的对象示例。当然,当指定的程序类名字不能在当前的classpath目录路径中找到时,getResource()功能方法将返回null。
java.lang.Thread类中的contextClassLoader属性则代表线程的上下文 ClassLoader,因为每个运行中的线程都有一个成员contextClassLoader属性对象,用来在运行时动态地载入其它的程序类。并且可以采用下面形式的程序代码获得当前线程的contextClassLoader对象实例。
ClassLoader currentClassLoader= Thread.currentThread().getContextClassLoader();
因此,如果在某个线程中需要应用并加载某个外部的功能类,可以首先利用Thread.currentThread().getContextClassLoader()功能方法获得该线程的对象实例的contextClassLoader;然后再利用所获得的contextClassLoader对象实例请求它的父ClassLoader对象实例来完成外部程序类的载入请求。
完整的ClassNameConfig程序类的功能实现代码请见下面的程序代码示例所示——ClassNameConfig类的功能实现代码示例
package com.px1987.webbank.config;import java.util.Properties;import java.io.*;public class ClassNameConfig { public ClassNameConfig() { } private static Properties onePropertiesObject =null; static { onePropertiesObject = new Properties(); try{ InputStream oneInputStreamObject = Thread.currentThread().getContextClassLoader(). getResource("classNameConfig.properties").openStream(); onePropertiesObject.load(input); }catch(Exception e){ e.printStackTrace(); } } public static String getProperty(String propName){ return onePropertiesObject.getProperty(propName); } public static void setProperty(String propName, String prop Value){ onePropertiesObject.setProperty(propName, propValue); }}
请读者注意其中的黑体标识的语句,由于static静态成员属性对象能够达到全局缓存的应用效果,并且static语句块是在该程序类被加载时就执行的、而且也只执行一次。因此,static语句块也能够产生一种"单例"(单一对象实例,也就是单例设计模式)的应用效果,避免重复地对属性配置文件(*.properties)进行加载。另外,下面的语句代码片段:
InputStream oneInputStreamObject =Thread.currentThread().getContextClassLoader().getResource("classNameConfig.properties").openStream();
其实等同于下面的程序代码:
ClassLoader currentClassLoader=Thread.currentThread().getContextClassLoader();URL oneURL = currentClassLoader.getResource("classNameConfig.properties");InputStream oneInputStreamObject = oneURL.openStream();
如何正确地创建和销毁软件应用系统中JDBC数据库连接对象实例
在程序中如何正确地创建和销毁软件应用系统中文件IO流对象实例
如何正确地创建和销毁网络通讯程序中的Socket类的对象实例
如何应用Java反射技术灵活地创建程序类的对象实例
如何应用GOF设计模式中的构建者模式创建复合对象实例