出处 http://msdn.microsoft.com
在软件开发进行了若干年后,设计模式逐渐被更多的程序员理解和采用。对常见的特定问题总是有其解决办法,这些解决办法逐渐得到了大家的公认。许多这样的解决办法被汇总整理成设计模式用来解决很多编程中的问题。基于这一点,微软公司提供了一个专栏来讨论各种实用的模式来帮助大家更快的解决开发过程中的问题。
(http://msdn.microsoft.com/msdnmag/issues/03/03/designpatterns/default.aspx)
在设计模式中经常用的一种叫做:工厂模式。工厂模式的好处是使用动态创建对象(甚至只靠对象的类型来创建对象)来降低对象间的耦合。可是当工厂方法和其他模式一起使用来创建高内聚低耦合的代码的时候,工厂方法并不是值得推荐的。使用.Net的反射机制使用C#语言来创建一个工厂类(没有写最常用的实现的部分)。
关于软件设计中的两大原则:高内聚低耦合的好处我就不多讲了。
工厂设计模式:
举个例子来说明工厂模式的好处:比如A对象需要向B对象发送消息,A必须持有一个B对象的引用来完成这个功能,也就是说:B对象必须经过初始化并且A对象可以访问他。我们大家最长使用的方法就是在A对象中直接初始化B对象,直接持有一个对B对象的引用,这样A对象就必须知道怎样初始化B对象,比如:需要那些初始化的参数等等,有经验的程序员都知道这样编程思路写下的程序的弊端。
怎样解决上面的问题呢。
答案是把创建B对象的工作交给另一个类:C
也就是说:所有需要使用B类的地方统一从C那里后的一个B的实例。
这就是工厂方法的功能。
当然,我们不可能完全消除耦和,这种方法是降低了程序的耦合程度,有利于程序以后的修改和扩充。上面采用类C来创建B的方法,还有更好的改进,就是使用接口,也就是我们常说的 :纯虚工厂,那样耦和的程度就更低了。大家可以参考具体的参考资料。
工厂模式中有三个主体需要我们分清: 1、工厂 2、客户 3、 产品
客户是:任何需要使用工厂提供对象的对象。
产品是:工厂生成的一个返回到客户的对象。
使用工厂模式的一般方法
工厂模式有很多个版本,不同的做法各有优劣。我们下面比较最常用的两种工厂模式后介绍使用反射机制的工厂模式。
据个例子:
假如你有一个管理电脑零件的程序,其中:
InventoryMgr , 是客户,仓库管理员。
PartsFactory , 是工厂;
MonitorInvFactory , 是显示器工厂;
KeyboardInvFactory, 是键盘工厂;
IpartsInventory , 是上面的工厂返回的产品。
UML图如下:
下面我把上面的图给大家解释一下:
MainClass 是用来向IventoryMgr来发送一个需要仓库对象请求的类。比如现在我们需要补充仓库的显示器的数量,就可以向IventoryMgr发送这个请求。代码如下:
class MainClass {
static void
PartsFactory myfactory = null;
InventoryMgr myinvmgr = new InventoryMgr();
foreach(string marg in args){
switch(marg) {
case "Monitors":
myfactory = new MonitorInvFactory();
break;
case "Keyboards":
myfactory = new KeyboardInvFactory();
break;
default:
break;
}
if(myfactory != null)
myinvmgr.ReplenishInventory(myfactory);
myfactory = null;
}
}
}
class InventoryMgr {
public void ReplenishInventory(PartsFactory vfactory) {
IPartsInventory PartInv = vfactory.ReturnPartInventory();
PartInv.Restock();
}
}
interface IPartsInventory {
void Restock();
}
class MonitorInventory : IPartsInventory {
public void Restock() {
Console.WriteLine("The monitor inventory has been restocked");
}
}
class KeyboardInventory : IPartsInventory {
public void Restock() {
Console.WriteLine("The keyboard inventory has been restocked");
}
}
abstract class PartsFactory {
public abstract IPartsInventory ReturnPartInventory();
}
class MonitorInvFactory : PartsFactory {
public override IPartsInventory ReturnPartInventory() {
return (IPartsInventory) new MonitorInventory();
}
}
class KeyboardInvFactory : PartsFactory {
public override IPartsInventory ReturnPartInventory() {
return (IPartsInventory) new KeyboardInventory();
}
}
上面的代码是可以说是一种工厂模式的合理的实现,但是有些不足的地方。
大家可以从耦和的角度看看上面工厂模式的代码实现。一个一个来:
InventoryMgr 没的说,耦合程度很低,他使用接口屏蔽了创建具体的类的信息。即使你增加新的零件(新工厂)也不会影响他。这就是:对扩展开发,对修改关闭的典型例子。同时InventoryMgr 也对实现不同的IpartsInventory 具有低的耦和。
那问题到底在哪里呢?
问题在MainClass中,他违背了“不要和陌生人说话”的公理。
因为MainClass中使用了创建具体类的工厂。从上面的代码可以看出MainClass实际上在参数未传达到之前并不知道具体的工厂,而且他只是简单的创建好类后有给了InventoryMgr 。 上面的做法实际上把:MainClass 、MonitorInvFactory、KeyboardInvFactory 等本应该不必要的耦和放到了一起。
一般来讲:一个对象中创建另一个对象的原则是:这个对象需要向其创建的对象发送消息,除非这个对象的任务就是创建和返回其他对象。工厂模式就是这样的一个例子。
MainClass的另一个弊端是他不是高内聚的。
MainClass 不应该考虑到底使用那个具体类来处理请求,他只是派发消息的代理。MainClass的作用只是接受客户消息请求,然后传达给InventoryMgr 做进一步的处理。使用反射机制可以更好的解决这个问题。
我们现在看看工厂方法的另一个常见的做法:
不使用抽象工厂创建对象,而以具体工厂创建对象代之。
这样做代码可以简化不少。
MainClass
InventoryMgr
PartsFactory
有一些变化
IpartsInventory 没有改变
MonitorInvFactory 不再需要
KeyboardInvFactory 不再需要 因为一个具体的工厂代替他们使用了。
最大的变化是使用了一个迭代器:enmInvParts。
代码如下:
class MainClass {
static void
InventoryMgr InvMgr = new InventoryMgr();
foreach(string marg in args){
switch(marg) {
case "Monitors":
InvMgr.ReplenishInventory(enmInvParts.Monitors);
break;
case "Keyboards":
InvMgr.ReplenishInventory(enmInvParts.Keyboards);
break;
default:
break;
}
}
}
}
public enum enmInvParts : int {Monitors = 1, Keyboards=2}
class InventoryMgr {
public void ReplenishInventory(enmInvParts InventoryPart) {
PartsFactory factory = new PartsFactory();
IPartsInventory IP = factory.ReturnPartInventory(InventoryPart);
IP.Restock();
}
}
class PartsFactory {
public IPartsInventory ReturnPartInventory(enmInvParts InvPart) {
IPartsInventory InvType;
switch(InvPart) {
case enmInvParts.Monitors:
InvType = new MonitorInventory();
break;
case enmInvParts.Keyboards:
InvType = new KeyboardInventory();
break;
default:
InvType = null;
break;
}
return InvType;
}
}
我们现在分析一下上面的代码:
上面提到的MainClass的弊端已经得到了改进。
MainClass不会在和“陌生人说活”了,但是他不得不和工厂耦和。使用enmInvParts 来包装客户端的请求。对客户端请求的包装是合理的,他也简单的把消息给了InventoryMgr 。
明眼人都能看出来:MainClass的耦和问题转嫁给了PartsFactory。并且PartsFactory 和IpartsInventory也出现了耦和。
让我们来看看反射机制如何实现低耦和这个崇高的目标吧。