接上篇,本文将记录去除简单工厂里的switch语句的方法与单例模式的使用,先说如何去除switch语句。
比较简单的实现是在每次程序开始运行为每一个类实例化一个PatternTester子类对象,然后将这个对象与对应的patterType字段一起作为Dictionary<string,PatternTester>数据结构中的一个元素,当客户端请求某个类型的实例对象时,根据指定的patternType字段在Dictionary<string,PatternTester>中查找到对应的对象再返回给客户端即可,这样实现的PatternTesterSimpleFactory类如下:
public class PatternTesterSimpleFactory
{
private Dictionary<string,PatternTester>testerList = null;
//公开访问级别的构造函数
public PatternTesterSimpleFactory()
{
InitPatternTesterList ();
}
private void InitPatternTesterList()
{
testerList = newDictionary<string, IPatternTester>();
testerList.Add(“Strategy”,new StrategyPatternTester());
testerList.Add(“Proxy”,new ProxyPatternTester());
}//如果没有使用继承,就不能将接口的返回值类型统一为PatternTester
//继承允许我们方便的扩展PatternTester,添加更多独特的Tester
public PatternTesterCreatePatternTester(string patternType)
{
PatternTester tester = null;
try
{
Tester = testerList[patternName];
}
catch
{
Tester = newNotInBookPatternTester();
}
retutn tester;
}
}
去除了switch语句,但是每当增加了新的PatternTester子类,还是要修改InitPatterTesterList()函数,将新增加的子类实例对象添加到PatterTesterList中,不方便,这时候C#的反射技术可以去除这一不便。反射的概念比较复杂,我也不是很清楚(C#初学者),但是我们这里要用到的技术可以这样陈述:根据指定的类的名称如StrategyPatternTester,ProxyPatternTester等)产生类的实例对象。为了完成该目标,需要.NET提供的Assembly类来完成。Assembly类代表一个程序集,程序集是.NET应用程序的部署单元,具体的定义我很难说清楚,通常来说他由一个或多个文件组成,可以是包含元数据的.dll文件或.exe文件,也还可能包含资源文件。Assembly类可以根据组成某一程序集的文件的名称加载程序集,此时Assembly类知道该程序集中有哪些类,通过Assembly类提供的一个以类名作为输入参数CreateInstance静态方法就可以创建类的实例对象了。那么程序如何知道实例化程序集中的那些类呢?并不是程序集中的每一个类需要实例化,也不是每个类都能实例化(如抽象类),这个时候有两种方式,将需要实例化的类组织到一个程序集中,该程序集不再含有其他类,这样每个类都实例化,而不必指明特定的类名,每个类对应的patternType字段可以从类名中提取(为类命名时严格遵循一致的规则),这样就能完成patterTesterList的初始化;第二种方法是知道程序集的文件名,通过一个XML文件告诉程序实例化程序集中的哪些类,这个XML文件中除了记录有需要实例化的类名之外,还有每个类对应的patternType字段,这样给类命名时的规则就可以不那么严格遵循,当然遵循一致的命名规则总是有利的。下面是这个XML文件的示例:
<?xml version="1.0"encoding="UTF-8"?>
<PatternTesters>
<TesterClass name="StrategyPatternTester"typeKey="Strategy"/>
<TesterClass name="ProxyPatternTester"typeKey="Proxy" />
</PatternTesters>
这个文件在每次添加新完PatternTester子类后,需要手工添加新的TesterClass节点,但是为了尽量少出错,后面将编写一个自动生成PatternTester子类代码的小程序,让这个小程序在生成子类的同时修改相关的XML文件。
选择第二种方式来改善PatternTesterSimpleFactory类的InitPatternTesterList方法。
class PatternTesterFactory
{
//私有的构造函数???理由见后文
private PatternTesterFactory()
{
}//使用static关键字的理由不仅仅是因为构造函数被
//设成了私有的,更重要的理由见后文
private staticDictionary<string, IPatternTester> testerList = null;
private static voidInitPatternTesterList()
{if (testerList ==null)
{
testerList = newDictionary<string, IPatternTester>();
XmlDocumenttesterConfigDoc = new XmlDocument();
// PatternTesterConfig.xml为配置文件名,利用.NET提供的XMLDocument
//类解析配置文件,提取需要的信息
testerConfigDoc.Load("PatternTesterConfig.xml");
XmlNode testerNode= testerConfigDoc.SelectSingleNode("/PatternTesters");
XmlNodeListtesterClassList = testerNode.ChildNodes;
stringtesterAssemblyName = "DesignPatternProject";
AssemblypatternTesterAssembly = Assembly.Load(testerAssemblyName);
foreach (XmlNodexmlNode in testerClassList)
{
stringtesterClassName = xmlNode.Attributes["name"].Value;
stringtypeKeyString = xmlNode.Attributes["typeKey"].Value;
// CreateInstance()方法的输入参数包括了testerAssemblyName
//类所在命名空间(namespace)的名称,本程序中与程序集名称一
//样,我不知道命名空间是否必须作为输入参数,没有做实验
testerList.Add(typeKeyString,(IPatternTester)patternTesterAssembly.CreateInstance(testerAssemblyName +"." + testerClassName));
}
}
}}
至此,完全消除了,确切的说,将其移到Dictionary<string,PatternTester>中了。
下面我们来说单例(Singleton)模式,单例模式可以保证一个类只有一个实例,这样做的目的是防止一些不应该同时存在多个实例对象意外地产生不止一个实例,也许是出于安全考虑,也许是出于性能考虑。
标准的单例模式实现如下:
public class Singleton
{
//这个类的实际对象是静态的
private static Singletoninstance = null;
//构造函数是私有级别的,外部不能调用,所以外部不能显式地创建该类的实例
private Singleton()
{
}
//外部只能通过类的编写者提供的接口实例化单例类,
//这是一个静态方法,类的编写者完全控制实例的产生public staticCreateInstance()
{
//只有静态变量instance没有被实例化时,
//才实例化一个真正的Singleton对象,
If(instance == null)
{
instance = newSingleton();
}
//否则,直接返回原来的已经实例化的对象
return instance;
}}
在一个程序中,单例类实例大多情况下是一个全局变量,再加上静态变量的语意,足以保证一个类只有一个实例。
PatternTesterFactory类在整个程序中只需要一个,从安全角度来讲有多个也无所谓,但是从性能上来讲,一个工厂就足以提供需要的类实例了,所以使用单例模式很合适。但是这里我们不需要用标准的方式来实现这个工厂类,我们的重点不是工厂类的实例,而是工厂类中testerList的实例,所以只需将testerList成员设成静态的,再将PatternTesterFactory类的构造函数设为私有的,一个应用了单例模式的工厂类就降生了。
每次编写一个新的PatternTester子类时,覆写虚函数总是重复的输入函数签名,编写完成后还需要修改配置文件PatternTesterConfig.xml,非常不方便,所以开发一个代码复制程序可以节约不少时间,这个程序没有什么技巧而言,只是生成一个新的文件,把一个PatternTester子类中固定代码作为文件内容,考虑到有的子类并不需要复写父类中的所有虚函数,所以程序允许代码编写者选择覆写哪些虚函数,然后将要覆写的虚函数函数体添加到文件中。文件生成成功后,再将新类的名称和标识该子类的patternType字段添加到PatternTesterConfig.xml中。这个工具的界面如下图所示:
另一个问题是,测试平台运行时需要知道到底是要测试哪一个模式,为了避免在Main函数中指定测试的模式,再添加一个表示当前测试模式的配置文件curTestConfig.xml,上面提到的程序生成工具在生成子类文件后,根据用户的选择决定是否修改curTestConfig.xml文件。curTestConfig.xml文件结构如下:
<?xml version="1.0" encoding="UTF-8"?>
<CurrentTestPattern name = "Proxy">
</CurrentTestPattern>
该文件中的name属性值总是与PatternTesterConfig.xml文件中某个patternType属性值相同。当然也可以在Main函数中创建一个Windows窗体展示当前可以测试的模式,测试者使用鼠标就可以完成将要测试的模式的设置,本系列文章将使用配置文件的方式。
然而,到目前为止完成的这个测试平台还有一个很重要的缺陷,这可以通过另外一个模式来解决——原型(Prototype)模式,这是下一篇文章将要记录的。