从子类化到工厂模式
工厂模式是最常用和最简单的设计模式,其与以抽象、多态为特征的oo结合的十分紧密,在以前的一篇介绍mvc框架切入点的随笔中对这一点就有过论证。
首先看一段代码
abstract class Animal { public abstract void Shout(); } class Dog : Animal { public override void Shout() { Console.WriteLine("wangwang"); } } class Cat : Animal { public override void Shout() { Console.WriteLine("miao"); } } //... public static void Main() { var name = Console.ReadLine(); Animal animal = null; if (name == "Dog") animal = new Dog(); else if (name == "Cat") animal = new Cat(); animal.Shout(); }
很简单的实例,却充满了臭味,为了实现功能,调用者必须熟知Animal的整个继承体系。那么应该怎么改进呢?启用工厂模式,看下面的代码:
public static class AnimalFactory { public static Animal Create(String name) { if (name == "Dog") return new Dog(); else if (name == "Cat") return new Cat(); return null; } } public static void Main() { var animal = AnimalFactory.Create(Console.ReadLine()); if(animal!=null) animal.Shout(); } //或者去掉Animal的abstract,使用基类实现工厂 class Animal { private Animal() { } public virtual void Shout() { } public static Animal Create(String name) { if (name == "Dog") return new Dog(); else if (name == "Cat") return new Cat(); return null; } }
好了,不同层之间的分离完成,基本的目的已经达到,那么就完了么?还没,当子类扩张的时候,我们必须进入工厂内部去修改分支语句,这就违反了封闭开放原则。那么怎么改进呢?使用表格,实现如下:
public static class AnimalFactory { static AnimalFactory() { children.Add("Dog", typeof(Dog)); children.Add("Cat", typeof(Cat)); } public static Animal Create(String name) { if (children.Keys.Contains(name)) return Activator.CreateInstance(children[name]) as Animal; return null; } public static Dictionary<String, Type> children; }
上面的实现是一种最简单的基于表格驱动的工厂模式,内部基于反射Type的表格的数据来源于自己手动去注册。基于表格驱动的工厂模式的核心在于表格数据的来源,其来源有下面几种:
1).手动注册,如上面显示的;
2).使用attribute,然后通过放射,在静态初始化函数中对相关的程序集进行扫描,进行表格的填充,简单显示如下:
public class AnimalMapAttribute : Attribute { public String Name { get; set; } } [AnimalMap(Name="cat")] public class AnimalMapAttribute : Attribute { public String Name { get; set; } }
3).使用配置文件进行键值对的登记,然后解析配置文件进行加载,还记得asp.net mvc配置文件中对handler进行注册的
<add name="xx" path="*." verb="xx" type="xx" />
语句么,使用配置文件进行注册的方式大抵如此。
具体的实现模式按照需求选择,如果子类数量固定或者代码可以很方便进行修改和编译,使用分支语句就行,因为基于表格驱动的模式需要用到反射。其实还有一种情况也可以勉强成为工厂模式,如下
class Animal { private Animal() { } public virtual void Shout() { } public static Animal Cat { get { return new Cat(); } } public static Animal Dog { get { return new Dog(); } } }
将子类以静态属性的形式写于基类里面,然后外部通过Animal.Cat进行访问就行。BCL中的Encoding类就是以这种方式暴露得编码类接口。同上,适用于子类固定的情形。