您的位置:首页 > 编程语言 > C#

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

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

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

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



上图的设计思路是:

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

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

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

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

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

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



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

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

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

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

Product.cs

/// <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 "柱子";
}
}
③然后是具体实现工厂接口的工厂类:实现接口返回一个具体的产品对象

Factory.cs

/// <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();
}
}
 ④最后是工厂管理类:组织起众多的产品与工厂

FactoryManager.cs

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;
}
}
按照国际惯例,我们实现一个入口方法来测试一下:

Client.cs

class Client
{
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();
}
}


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

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

  

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

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

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

/// <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>
[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 "小米柱子";
}
}
③下面是修改后的工厂类,由于使用了反射特性,这里一个工厂类型就可以生产所有的产品:

Factory.cs

public class Factory
{
public IProduct Product(RoomParts parts)
{
ProductListAttribute attr =
(ProductListAttribute)System.Attribute.GetCustomAttribute(typeof(IProduct), typeof(ProductListAttribute));
foreach (var type in attr.ProductList)
{
ProductAttribute pa =
(ProductAttribute)System.Attribute.GetCustomAttribute(type, typeof(ProductAttribute));
if (pa.RoomPart == parts)
{
object product = Assembly.GetExecutingAssembly().CreateInstance(type.FullName);
return product as IProduct;
}
}

return null;
}
}

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

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

FactoryManager.cs

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();

Factory factory = new Factory();
IProduct product = factory.Product(part);
Console.WriteLine("生产了一个产品:{0}", product.GetName());
return product;
}
}

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

作者:周旭龙

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

来自为知笔记(Wiz)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: