C#如何用反射实现工厂模式?

工厂模式是一种比较常用的设计模式,其基本思想在于使用不同的工厂类型来打造不同产品的部件。例如,我们在打造一间屋子时,可能需要窗户、屋顶、门、房梁、柱子等零部件。有的屋子需要很多根柱子,而有的屋子又不需要窗户。在这样的需求下,就可以使用工厂模式。.

(1)工厂模式的传统实现和其弊端

下图展示了针对屋子设计的传统工厂模式架构图:

C#如何用反射实现工厂模式?

上图的设计思路是:

① 使用者告诉工厂管理者需要哪个产品部件;

② 工厂管理者分析使用者传入的信息,生成合适的实现工厂接口的类型对象;

③ 通过工厂生产出相应的产品,返回给使用者一个实现了该产品接口的类型对象;

通过上述思路,实现代码如下:

① 首先是定义工厂接口,产品接口与产品类型的枚举

/// <summary>
/// 屋子产品的零件
/// </summary>
public enum RoomParts
{
    Roof,
    Window,
    Pillar
}

/// <summary>
/// 工厂接口
/// </summary>
public interface IFactory
{
    IProduct Produce();
}

/// <summary>
/// 产品接口
/// </summary>
public interface IProduct
{
    string GetName();
}

② 其次是具体实现产品接口的产品类:窗户、屋顶和柱子

/// <summary>
/// 屋顶
/// </summary>
public class Roof : IProduct
{
    // 实现接口,返回产品名字
    public string GetName()
    {
        return "屋顶";
    }
}

/// <summary>
/// 窗户
/// </summary>
public class Window : IProduct
{
    // 实现接口,返回产品名字
    public string GetName()
    {
        return "窗户";
    }
}

/// <summary>
/// 柱子
/// </summary>
public class Pillar : IProduct
{
    // 实现接口,返回产品名字
    public string GetName()
    {
        return "柱子";
    }
}

③ 然后是具体实现工厂接口的工厂类:实现接口返回一个具体的产品对象

/// <summary>
/// 屋顶工厂
/// </summary>
public class RoofFactory : IFactory
{
    // 实现接口,返回一个产品对象
    public IProduct Produce()
    {
        return new Roof();
    }
}

/// <summary>
/// 窗户工厂
/// </summary>
public class WindowFactory : IFactory
{
    // 实现接口,返回一个产品对象
    public IProduct Produce()
    {
        return new Window();
    }
}

/// <summary>
/// 柱子工厂
/// </summary>
public class PillarFactory : IFactory
{
    // 实现接口,返回一个产品对象
    public IProduct Produce()
    {
        return new Pillar();
    }
}

④ 最后是工厂管理类:组织起众多的产品与工厂

/// <summary>
/// 工厂管理者
/// </summary>
public class FactoryManager
{
    public static IProduct GetProduct(RoomParts part)
    {
        IFactory factory = null;
        // 传统工厂模式的弊端:工厂管理类和工厂类族的紧耦合
        switch (part)
        {
            case RoomParts.Roof:
                factory = new RoofFactory();
                break;
            case RoomParts.Window:
                factory = new WindowFactory();
                break;
            case RoomParts.Pillar:
                factory = new PillarFactory();
                break;
            default:
                return null;
        }

        // 利用工厂生产产品
        IProduct product = factory.Produce();
        Console.WriteLine("生产了一个产品:{0}", product.GetName());

        return product;
    }
}

按照国际惯例,我们实现一个入口方法来测试一下:

public class Customer
{
    public static void Main(string[] args)
    {
        // 根据需要获得不同的产品零件
        IProduct window = FactoryManager.GetProduct(RoomParts.Window);
        Console.WriteLine("我获取到了{0}",window.GetName());
        IProduct roof = FactoryManager.GetProduct(RoomParts.Roof);
        Console.WriteLine("我获取到了{0}", roof.GetName());
        IProduct pillar = FactoryManager.GetProduct(RoomParts.Pillar);
        Console.WriteLine("我获取到了{0}", pillar.GetName());

        Console.ReadKey();
    }
}

在Customer类中,我们通过工厂管理类根据需要的不同零件类型获取到了不同的产品零件,其运行结果如下图所示:

C#如何用反射实现工厂模式?

当一个新的产品—地板需要被添加时,我们需要改的地方是:添加零件枚举记录、添加针对地板的工厂类、添加新地板产品类,修改工厂管理类(在switch中添加一条case语句),这样设计的优点在于无论添加何种零件,产品使用者都不需要关心内部的变动,可以一如既往地使用工厂管理类来得到希望的零件,而缺点也有以下几点:

① 工厂管理类和工厂类族耦合;

② 每次添加新的零件都需要添加一对工厂类和产品类,类型会越来越多;

(2)基于反射的工厂模式的实现

利用反射机制可以实现更加灵活的工厂模式,这一点体现在利用反射可以动态地获知一个产品由哪些零部件组成,而不再需要用一个switch语句来逐一地寻找合适的工厂。

① 产品、枚举和以上一致,这里的改变主要在于添加了两个自定义的特性,这两个特性会被分别附加在产品类型和产品接口上:

/// <summary>
/// 该特性用于附加在产品类型之上
/// </summary>
[AttributeUsage(AttributeTargets.Class)]
public class ProductAttribute : Attribute
{
    // 标注零件的成员
    private RoomParts myRoomPart;

    public ProductAttribute(RoomParts part)
    {
        myRoomPart = part;
    }

    public RoomParts RoomPart
    {
        get
        {
            return myRoomPart;
        }
    }
}

/// <summary>
/// 该特性用于附加在产品接口类型之上
/// </summary>
[AttributeUsage(AttributeTargets.Interface)]
public class ProductListAttribute : Attribute
{
    // 产品类型集合
    private Type[] myList;

    public ProductListAttribute(Type[] products)
    {
        myList = products;
    }

    public Type[] ProductList
    {
        get
        {
            return myList;
        }
    }
}

② 下面是产品接口和产品类族的定义,其中产品接口使用了ProductListAttribute特性,而每个产品都使用了ProductAttribute特性:

/// <summary>
/// 产品接口
/// </summary>
[ProductList(new Type[] { typeof(Roof), typeof(Window), typeof(Pillar) })]
public interface IProduct
{
    string GetName();
}

/// <summary>
/// 屋顶
/// </summary>
[Product(RoomParts.Roof)]
public class Roof : IProduct
{
    // 实现接口,返回产品名字
    public string GetName()
    {
        return "小天鹅屋顶";
    }
}

/// <summary>
/// 窗户
/// </summary>
[Product(RoomParts.Window)]
public class Window : IProduct
{
    // 实现接口,返回产品名字
    public string GetName()
    {
        return "双汇窗户";
    }
}

/// <summary>
/// 柱子
/// </summary>
[Product(RoomParts.Pillar)]
public class Pillar : IProduct
{
    // 实现接口,返回产品名字
    public string GetName()
    {
        return "小米柱子";
    }
}

③ 下面是修改后的工厂类,由于使用了反射特性,这里一个工厂类型就可以生产所有的产品:

/// <summary>
/// 工厂类
/// </summary>
public class Factory
{
    public IProduct Produce(RoomParts part)
    {
        // 通过反射从IProduct接口中获得属性从而获得所有产品列表
        ProductListAttribute attr = (ProductListAttribute)Attribute.GetCustomAttribute(typeof(IProduct), typeof(ProductListAttribute));
        // 遍历所有的实现产品零件类型
        foreach (var type in attr.ProductList)
        {
            // 利用反射查找其属性
            ProductAttribute pa = (ProductAttribute)Attribute.GetCustomAttribute(type, typeof(ProductAttribute));
            // 确定是否是需要到的零件
            if(pa.RoomPart == part)
            {
                // 利用反射动态创建产品零件类型实例
                object product = Assembly.GetExecutingAssembly().CreateInstance(type.FullName);
                return product as IProduct;
            }
        }

        return null;
    }
}

④ 最后时修改后的工厂管理类,核心只有三行代码:

/// <summary>
/// 工厂管理者
/// </summary>
public class FactoryManager
{
    public static IProduct GetProduct(RoomParts part)
    {
        // 一共只有一个工厂
        Factory factory = new Factory();
        IProduct product = factory.Produce(part);
        Console.WriteLine("生产了一个产品:{0}", product.GetName());
        return product;
    }
}

上述代码中最主要的变化在于两点:

其一是工厂管理类不再需要根据不同的零件寻找不同的工厂,因为只有一个工厂负责处理所有的产品零件;

其二是产品类型和产品接口应用了两个自定义特性,来方便工厂进行反射。ProductAttribute附加在产品类上,标注了当前类型代表了哪个产品零件。而ProductListAttribute则附加在产品接口之上,方便反射得知一共有多少产品零件。

这时需要添加一个新的地板产品零件类型时,我们需要做的是:添加零件枚举记录,添加代表地板的类型,修改添加在IProduct上的属性初始化参数(增加地板类型),可以看到这时调用者、工厂管理类和工厂都不再需要对新添加的零件进行改动,程序只需要添加必要的类型和枚举记录即可。

当然,这样的设计也存在一定缺陷:反射的运行效率相对较低,在产品零件相对较多时,每生产一个产品就需要反射遍历这是一件相当耗时的工作