C#回顾 - 7.如何使用反射实现工厂模式?

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

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

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

上图的设计思路是:

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

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

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


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

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

IFactory.cs(工厂接口,产品接口一起

  1. /// <summary>  
  2. /// 屋子产品的零件  
  3. /// </summary>  
  4. public enum RoomParts  
  5. {  
  6.     Roof,  
  7.     Window,  
  8.     Pillar  
  9. }  
  10.   
  11. /// <summary>  
  12. /// 工厂接口  
  13. /// </summary>  
  14. public interface IFactory  
  15. {  
  16.     IProduct Produce();  
  17. }  
  18.   
  19. /// <summary>  
  20. /// 产品接口  
  21. /// </summary>  
  22. public interface IProduct  
  23. {  
  24.     string GetName();  
  25. }  

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

Product.cs

  1. /// <summary>  
  2.  /// 屋顶  
  3.  /// </summary>  
  4.  public class Roof : IProduct  
  5.  {  
  6.      // 实现接口,返回产品名字  
  7.      public string GetName()  
  8.      {  
  9.          return "屋顶";  
  10.      }  
  11.  }  
  12.   
  13.  /// <summary>  
  14.  /// 窗户  
  15.  /// </summary>  
  16.  public class Window : IProduct  
  17.  {  
  18.      // 实现接口,返回产品名字  
  19.      public string GetName()  
  20.      {  
  21.          return "窗户";  
  22.      }  
  23.  }  
  24.   
  25.  /// <summary>  
  26.  /// 柱子  
  27.  /// </summary>  
  28.  public class Pillar : IProduct  
  29.  {  
  30.      // 实现接口,返回产品名字  
  31.      public string GetName()  
  32.      {  
  33.          return "柱子";  
  34.      }  
  35.  }  

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

Factory.cs

  1. /// <summary>  
  2.  /// 屋顶工厂  
  3.  /// </summary>  
  4.  public class RoofFactory : IFactory  
  5.  {  
  6.      // 实现接口,返回一个产品对象  
  7.      public IProduct Produce()  
  8.      {  
  9.          return new Roof();  
  10.      }  
  11.  }  
  12.   
  13.  /// <summary>  
  14.  /// 窗户工厂  
  15.  /// </summary>  
  16.  public class WindowFactory : IFactory  
  17.  {  
  18.      // 实现接口,返回一个产品对象  
  19.      public IProduct Produce()  
  20.      {  
  21.          return new Window();  
  22.      }  
  23.  }  
  24.   
  25.  /// <summary>  
  26.  /// 柱子工厂  
  27.  /// </summary>  
  28.  public class PillarFactory : IFactory  
  29.  {  
  30.      // 实现接口,返回一个产品对象  
  31.      public IProduct Produce()  
  32.      {  
  33.          return new Pillar();  
  34.      }  
  35.  }  

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

FactoryManager.cs

  1. class FactoryManager  
  2. {  
  3.     public static IProduct GetProduct(RoomParts part)  
  4.     {  
  5.         IFactory factory = null;  
  6.         //传统工厂模式的弊端,工厂管理类和工厂类的紧耦合  
  7.         switch (part)  
  8.         {  
  9.                 case RoomParts.Roof:  
  10.                 factory = new RoofFactory();  
  11.                 break;  
  12.             case RoomParts.Window:  
  13.                 factory = new WindowFactory();  
  14.                 break;  
  15.                 case RoomParts.Pillar:  
  16.                factory = new PillarFactory();  
  17.                 break;  
  18.             default:  
  19.                 return null;  
  20.         }  
  21.         IProduct product = factory.Produce();  
  22.         Console.WriteLine("生产了一个产品:{0}", product.GetName());  
  23.         return product;  
  24.     }  
  25. }  

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

Client.cs

  1. class Client  
  2. {  
  3.     static void Main(string[] args)  
  4.     {  
  5.         IProduct window = FactoryManager.GetProduct(RoomParts.Window);  
  6.         Console.WriteLine("我获取到了{0}", window.GetName());  
  7.         IProduct roof = FactoryManager.GetProduct(RoomParts.Roof);  
  8.         Console.WriteLine("我获取到了{0}", roof.GetName());  
  9.         IProduct pillar = FactoryManager.GetProduct(RoomParts.Pillar);  
  10.         Console.WriteLine("我获取到了{0}", pillar.GetName());  
  11.   
  12.         Console.ReadKey();  
  13.     }  
  14. }  



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

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

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

  

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

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

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

  1. /// <summary>  
  2. /// 该特性用于附加在产品类型之上  
  3. /// </summary>  
  4. [AttributeUsage(AttributeTargets.Class)]  
  5. public class ProductAttribute : Attribute  
  6. {  
  7.     // 标注零件的成员  
  8.     private RoomParts myRoomPart;  
  9.   
  10.     public ProductAttribute(RoomParts part)  
  11.     {  
  12.         myRoomPart = part;  
  13.     }  
  14.   
  15.     public RoomParts RoomPart  
  16.     {  
  17.         get  
  18.         {  
  19.             return myRoomPart;  
  20.         }  
  21.     }  
  22. }  
  23.   
  24. /// <summary>  
  25. /// 该特性用于附加在产品接口类型之上  
  26. /// </summary>  
  27. [AttributeUsage(AttributeTargets.Interface)]  
  28. public class ProductListAttribute : Attribute  
  29. {  
  30.     // 产品类型集合  
  31.     private Type[] myList;  
  32.   
  33.     public ProductListAttribute(Type[] products)  
  34.     {  
  35.         myList = products;  
  36.     }  
  37.   
  38.     public Type[] ProductList  
  39.     {  
  40.         get  
  41.         {  
  42.             return myList;  
  43.         }  
  44.     }  
  45. }  


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

  1. /// <summary>  
  2. /// 屋顶  
  3. /// </summary>  
  4. [Product(RoomParts.Roof)]  
  5. public class Roof : IProduct  
  6. {  
  7.     // 实现接口,返回产品名字  
  8.     public string GetName()  
  9.     {  
  10.         return "小天鹅屋顶";  
  11.     }  
  12. }  
  13.   
  14. /// <summary>  
  15. /// 窗户  
  16. /// </summary>  
  17. [Product(RoomParts.Window)]  
  18. public class Window : IProduct  
  19. {  
  20.     // 实现接口,返回产品名字  
  21.     public string GetName()  
  22.     {  
  23.         return "双汇窗户";  
  24.     }  
  25. }  
  26.   
  27. /// <summary>  
  28. /// 柱子  
  29. /// </summary>  
  30. [Product(RoomParts.Pillar)]  
  31. public class Pillar : IProduct  
  32. {  
  33.     // 实现接口,返回产品名字  
  34.     public string GetName()  
  35.     {  
  36.         return "小米柱子";  
  37.     }  
  38. }  

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

Factory.cs

  1. public class Factory  
  2. {  
  3.     public IProduct Product(RoomParts parts)  
  4.     {  
  5.         ProductListAttribute attr =  
  6.             (ProductListAttribute)System.Attribute.GetCustomAttribute(typeof(IProduct), typeof(ProductListAttribute));  
  7.         foreach (var type in attr.ProductList)  
  8.         {  
  9.             ProductAttribute pa =  
  10.                 (ProductAttribute)System.Attribute.GetCustomAttribute(type, typeof(ProductAttribute));  
  11.             if (pa.RoomPart == parts)  
  12.             {  
  13.                 object product = Assembly.GetExecutingAssembly().CreateInstance(type.FullName);  
  14.                 return product as IProduct;  
  15.             }  
  16.         }  
  17.   
  18.         return null;  
  19.     }  
  20. }  
  21.   
  22. ///// <summary>  
  23. ///// 屋顶工厂  
  24. ///// </summary>  
  25. //public class RoofFactory : IFactory  
  26. //{  
  27. //    // 实现接口,返回一个产品对象  
  28. //    public IProduct Produce()  
  29. //    {  
  30. //        return new Roof();  
  31. //    }  
  32. //}  
  33.   
  34. ... ...

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

FactoryManager.cs

  1. class FactoryManager  
  2.  {  
  3.      public static IProduct GetProduct(RoomParts part)  
  4.      {  
  5.          //IFactory factory = null;  
  6.          ////传统工厂模式的弊端,工厂管理类和工厂类的紧耦合  
  7.          //switch (part)  
  8.          //{  
  9.          //        case RoomParts.Roof:  
  10.          //        factory = new RoofFactory();  
  11.          //        break;  
  12.          //    case RoomParts.Window:  
  13.          //        factory = new WindowFactory();  
  14.          //        break;  
  15.          //        case RoomParts.Pillar:  
  16.          //       factory = new PillarFactory();  
  17.          //        break;  
  18.          //    default:  
  19.          //        return null;  
  20.          //}  
  21.          //IProduct product = factory.Produce();  
  22.   
  23.          Factory factory = new Factory();  
  24.          IProduct product = factory.Product(part);  
  25.          Console.WriteLine("生产了一个产品:{0}", product.GetName());  
  26.          return product;  
  27.      }  
  28.  }  

上述代码中最主要的变化在于两点:
其一是工厂管理类不再需要根据不同的零件寻找不同的工厂,因为只有一个工厂负责处理所有的产品零件;
其二是产品类型和产品接口应用了两个自定义特性,来方便工厂进行反射。
 ProductAttribute 附加在产品类上,标注了当前类型代表了哪个产品零件。而 ProductListAttribute 则附加在产品接口之上,方便反射得知一共有多少产品零件。
这时需要添加一个新的地板产品零件类型时,我们需要做的是:
1.添加零件枚举记录
  1. /// <summary>  
  2. /// 屋子产品的零件  
  3. /// </summary>  
  4. public enum RoomParts  
  5. {  
  6.     Roof,  
  7.     Window,  
  8.     Pillar,  
  9.     Floor //地板  
  10. }  
2.添加代表地板的类型(Product.cs)
  1. /// <summary>  
  2. /// 地板  
  3. /// </summary>  
  4. [Product(RoomParts.Floor)]  
  5. public class Floor : IProduct  
  6. {  
  7.     // 实现接口,返回产品名字  
  8.     public string GetName()  
  9.     {  
  10.         return "地板";  
  11.     }  
  12. }  
3.修改添加在IProduct上的属性初始化参数(增加地板类型),
  1.     /// <summary>  
  2.     /// 产品接口  
  3.     /// </summary>  
  4.     [ProductList(new Type[] { typeof(Roof), typeof(Window), typeof(Pillar)typeof(Floor) })]  
  5.     public interface IProduct  
  6.     {  
  7.         string GetName();  
  8.     }  
可以看到这时调用者、工厂管理类和工厂都不再需要对新添加的零件进行改动,程序只需要添加必要的类型和枚举记录即可。当然,这样的设计也存在一定缺陷:反射的运行效率相对较低,在产品零件相对较多时,每生产一个产品就需要反射遍历这是一件相当耗时的工作。


作者:周旭龙

出处:http://www.cnblogs.com/edisonchou/p/4827578.html


 



posted @ 2016-11-28 15:44  【唐】三三  阅读(1092)  评论(0编辑  收藏  举报