本文是一篇介绍如何搭建.NET灵活体系结构的文章。前一篇文章介绍了一种可满足应用程序绝大部分配置需求的方法。在那篇文章中,假定某一配置文件的具体内容如下所示: <GeneralConfiguration> <section1> <key1>value1</key1> </section1> <section2> <key name="x">value_for_x</key> <key name="y" a1="10" a2 ="15"> <a3>16</a3> <a4>17</a4> </key> </section2> </GeneralConfiguration>
可以通过使用下列描述性调用序列从上述配置文件中访问相关取值: // get a class responsible for implementing IConfig IConfig cfg; // reading a simple key cfg.getValue("/section1/key1") -> value1 // reading a key with reserved name attribute cfg.getValue("/section2/key/x") -> value_for_x // reading a key with reserved name attribute and its // attribute/child nodes cfg.getValue("/section2/key/y/a1") -> 10 cfg.getValue("/section2/key/y/a3") -> 16
使用IConfig诸多优点包括: · 提供了使用简单的API来访问信息层次结构的能力; · 不必取代 XPath,只需使用一些可行的约定来对其进行补充和完善; · 允许使用默认值; · 可区分必选值和可选值; · 可以在处理关键字取值时不区分大小写; · 基于特定的假设,可以使用相似的方式来处理属性和子元素。 工厂服务: 开始体系结构领域之旅 构建大型结构的关键就是要从简单的想法入手。这就是说,复杂性并不能构建在复杂性基础之上。接下来,我们将详细描述如何在这种简单的配置服务基础之上对其进行扩展并形成应用级的工厂服务。 究竟什么是工厂服务? 语言总会产生这样一种倾向:对流行的编程习惯或实践加以形式化,然后将其引入到语言的构建中。例如,Java 引入了关键字interface,而在Java之前的C++中这仅仅是一种没有正式化的概念。 C#同样具有关键字interface。同时,C#也通过关键字delegate对事件处理进行了形式化处理。为了能够更好地理解工厂服务的含义,需要首先来准确理解接口。之所以这样做,就是因为接口是体系结构的核心概念。 理解接口 接口就像一个具有一套方法的类。因此,如果您得到了一个对象并且明确它实现的接口,您就能以一种可靠的方式来调用这些方法。看看下面的代码,您就会发现它事实上还是很简单的: //Let us define an interface interface LoggingInterface { public void logMessage(String message); } //Let's see how we can use this interface public void myLoggingFunction( Object o) { if (o is LoggingInterface) { LoggingInterface log = (LoggingInterface)o; log.message("test message"); } else { // sorry not sure of the type of this object throw new Exception("Unexpected type"); } }
现在问题是当您调用logMessage函数时,谁将记录这一消息呢?上面的接口并没有相应代码来负责记录此消息,这应该怎么处理呢?这种情况下,就需要实现下列内容: public class FileLogger : LoggingInterface { private FileOutputStream fs = null; public FileLogger(string filename) { fs = new FileOutputstream(filename); } // implement the following method to complete the // contract required by LoggingInterface public void logMessage(String message) { fs.println(message); } public close() { fs.close(); } }
好了,您现在可以加入下列代码: FileLogger fileLogger = new FileLogger("myfile"); myLoggingFunction(fileLogger);
现在,提出这样一个要求:需要将所有的日志记录都写入到操作系统所支持的事件日志中。因此,创建一个新类来实现此需求,如下所示: public class EventLogger : ILog { ... other code public void logMessage(string message) { .. do the needful } }
将 FileLogger fileLogger = new FileLogger("myfile"); myLoggingFunction(fileLogger);
更改为 EventLogger eventLogger = new EventLogger(..); myLoggingFunction(eventLogger);
接下来您就是希望能够得到一个实用函数,该函数可为您提供一个正确的日志接口: LoggingInterface log = MyUtility.getLogger(); myLoggingFunction(log);
现在可以开始编写 MyUtility.getLogger() 函数,如下所示: public LoggingInterface getLogger() { return new FileLogger("myfile"); // return new EventLogger(); }
如果用挑剔的目光来看待目前的进展,您可能就会产生这样的感觉:很长一段时间以来一直忽略的配置服务可能将成为接下来需要认真考虑的内容。您设想有这样一个配置文件,如下所示: <request name="logger'> <classname>EventLogger </request>
很多语言中,只要知道了类名和它的继承或派生关系,就能很容易地对此类进行实例化。具备了这些信息,您就可以将 getLogger() 实用函数修改为: public LoggingInterface getLogger() { IConfig cfg = ... string loggerClassname = cfg.getValue ("/request/logger/classname","EventLogger"); Object o = instantiateAnObjectForItsClassname( loggerClassName); // code is not provided for this function LoggingInterface log = (LoggingInterface)o; return log; }
到现在为止,您的目标几乎完全实现了。您能够动态使用配置文件将一个日志类进行实例化。这时,您的需求也随之增长,您又希望此项功能可以为各种各样的类实例化动态对象,而不仅仅局限于当前的日志类。所以,您又再次对实用函数进行了修改: public static Object getObject(string interfacename) { IConfig cfg = ... string interfaceClassname = cfg.getValue("/request/" + interfacename + "/classname"); Object o = instantiateAnObjectForItsClassname( loggerClassName); // code is not provided for this function return o; }
|