3 完整解决方案
Sunny 公司开发人员决定使用工厂方法模式来设计日志记录器,其基本结构如图3 所示:
图3 日志记录器结构图
在图3中,Logger接口充当抽象产品,其子类FileLogger和DatabaseLogger充当具体产品,LoggerFactory接口充当抽象工厂,其子类FileLoggerFactory和DatabaseLoggerFactory充当具体工厂。完整代码如下所示:
interface Logger { public void writeLog(); } class DatabaseLogger implements Logger { public void writeLog() { System.out.println("数据库日志记录。" ); } } class FileLogger implements Logger { public void writeLog() { System.out.println("文件日志记录。" ); } } interface LoggerFactory { public Logger createLogger(); } class DatabaseLoggerFactory implements LoggerFactory { public Logger createLogger() { Logger logger = new DatabaseLogger(); return logger; } } class FileLoggerFactory implements LoggerFactory { public Logger createLogger() { Logger logger = new FileLogger(); return logger; } }
编写如下客户端测试代码:
class Client { public static void main(String args[]) { LoggerFactory factory; Logger logger; factory = new FileLoggerFactory(); logger = factory.createLogger(); logger.writeLog(); } }
编译并运行程序,输出结果如下:
4 反射与配置文件
为了让系统具有更好的灵活性和可扩展性,Sunny 公司开发人员决定对日志记录器客户端代码进行重构,使得可以在不修改任何客户端代码的基础上更换或增加新的日志记录方式。
在客户端代码中将不再使用new 关键字来创建工厂对象,而是将具体工厂类的类名存储在配置文件(如XML 文件)中,通过读取配置文件获取类名字符串,再使用Java 的反射机制,根据类名字符串生成对象。在整个实现过程中需要用到两个技术:Java 反射机制与配置文件读取。软件系统的配置文件通常为XML 文件,我们可以使用DOM (Document Object Model) 、SAX (Simple API for XML) 、StAX (Streaming API for XML) 等技术来处理XML 文件。关于DOM 、SAX 、StAX 等技术的详细学习大家可以参考其他相关资料,在此不予扩展。
扩展
关于Java 与XML 的相关资料,大家可以阅读Tom Myers 和Alexander Nakhimovsky 所著的《Java XML 编程指南》一书或访问developer Works 中国中的“Java XML 技术专题”,参考链接:
http://www.ibm.com/developerworks/cn/xml/theme/x-java.html
Java 反射(Java Reflection) 是指在程序运行时获取已知名称的类或已有对象的相关信息的一种机制,包括类的方法、属性、父类等信息,还包括实例的创建和实例类型的判断等。在反射中使用最多的类是Class ,Class 类的实例表示正在运行的Java 应用程序中的类和接口,其forName(String className) 方法可以返回与带有给定字符串名的类或接口相关联的 Class 对象,再通过Class 对象的newInstance() 方法创建此对象所表示的类的一个新实例,即通过一个类名字符串得到类的实例。如创建一个字符串类型的对象,其代码如下:
Class c=Class.forName("String" ); Object obj=c.newInstance(); return obj;
此外,在JDK 中还提供了java.lang.reflect 包,封装了其他与反射相关的类,此处只用到上述简单的反射代码,在此不予扩展。
Sunny 公司开发人员创建了如下XML 格式的配置文件config.xml 用于存储具体日志记录器工厂类类名:
<!— config.xml -- > <? xml version = "1.0" ?> < config > < className > FileLoggerFactory </ className > </ config >
为了读取该配置文件并通过存储在其中的类名字符串反射生成对象,Sunny 公司开发人员开发了一个名为XMLUtil 的工具类,其详细代码如下所示:
import javax.xml.parsers.*; import org.w3c.dom.*; import org.xml.sax.SAXException; import java.io.*; public class XMLUtil { public static Object getBean() { try { DocumentBuilderFactory dFactory = DocumentBuilderFactory.newInstance(); DocumentBuilder builder = dFactory.newDocumentBuilder(); Document doc; doc = builder.parse(new File( "config.xml" )); NodeList nl = doc.getElementsByTagName("className" ); Node classNode=nl.item(0 ).getFirstChild(); String cName=classNode.getNodeValue(); Class c=Class.forName(cName); Object obj=c.newInstance(); return obj; } catch (Exception e) { e.printStackTrace(); return null ; } } }
有了XMLUtil 类后,可以对日志记录器的客户端代码进行修改,不再直接使用new 关键字来创建具体的工厂类,而是将具体工厂类的类名存储在XML 文件中,再通过XMLUtil 类的静态工厂方法getBean() 方法进行对象的实例化,代码修改如下:
class Client { public static void main(String args[]) { LoggerFactory factory; Logger logger; factory = (LoggerFactory)XMLUtil.getBean(); logger = factory.createLogger(); logger.writeLog(); } }
引入XMLUtil 类和XML 配置文件后,如果要增加新的日志记录方式,只需要执行如下几个步骤:
(1) 新的日志记录器需要继承抽象日志记录器Logger ;
(2) 对应增加一个新的具体日志记录器工厂,继承抽象日志记录器工厂LoggerFactory ,并实现其中的工厂方法createLogger() ,设置好初始化参数和环境变量,返回具体日志记录器对象;
(3) 修改配置文件config.xml ,将新增的具体日志记录器工厂类的类名字符串替换原有工厂类类名字符串;
(4) 编译新增的具体日志记录器类和具体日志记录器工厂类,运行客户端测试类即可使用新的日志记录方式,而原有类库代码无须做任何修改,完全符合“开闭原则”。
通过上述重构可以使得系统更加灵活,由于很多设计模式都关注系统的可扩展性和灵活性,因此都定义了抽象层,在抽象层中声明业务方法,而将业务方法的实现放在实现层中。
思考
有人说:可以在客户端代码中直接通过反射机制来生成产品对象,在定义产品对象时使用抽象类型,同样可以确保系统的灵活性和可扩展性,增加新的具体产品类无须修改源代码,只需要将其作为抽象产品类的子类再修改配置文件即可,根本不需要抽象工厂类和具体工厂类。
试思考这种做法的可行性?如果可行,这种做法是否存在问题?为什么?