从子类化到工厂模式

  工厂模式是最常用和最简单的设计模式,其与以抽象、多态为特征的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类就是以这种方式暴露得编码类接口。同上,适用于子类固定的情形。

posted on 2013-08-22 09:14  wangjieas  阅读(209)  评论(0编辑  收藏  举报

导航