我是如何学习设计模式的十:工厂模式-这个是我花的心思最多的
工厂模式
为什么会出现
1—创建对象
2— 3我们在编程的时候,每当"new"一个对象之后,这个对象就依赖于这个类了。如果在后期的维护过程中由于某些原因需要修改一下这个类,则唯一的做法就是打开源代码,进行修改,修改所有与这个对象有关的操作。这对我们是非常不利的。
3— 问题出来了:对象不能应对“具体实例化类型”的变化
4— 解决思路:套用一下李建忠李老师的话,封装变化点,哪里变化,封装哪里。在这个例子中,要实例化的类变了,就将实例化这个操作封装起来,我们可以把"new"这个操作移交一个具体的类,由它去负责根据我们的条件创建具体类的实例,也就是下面要说的“简单工厂模式”。
理论
理论1—UML图解
下面是简单工厂模式的示意性UML图:
如上图,简单工厂模式UML我画了两种,详细如下:
① 只有一个产品对象的简单工厂模式。
② 带有一个抽象产品对象的简单工厂模式。
四、模式参与者
工厂(Factory)角色:接受客户端的请求,通过请求负责创建相应的产品对象。
抽象产品(AbstractProduct)角色: 是工厂模式所创建对象的父类或是共同拥有的接口。可是抽象类或接口。
具体产品(ConcreteProduct)对象:工厂模式所创建的对象都是这个角色的实例。
理论 前人整理好的易懂的例子
1. 写在前面
这篇文章是我酝酿了近一个月的时间写出来的,我想在第一个模式上就写出工厂模式,却一直推迟到现在。
工厂模式,看似很容易,很常见,但是学习设计模式两年,我至今仍未能真正地充分理解工厂模式的意图。工厂模式好在哪,他究竟解决了什么问题,工厂还是不工厂,困扰了我整整两年。
从无模式,到为模式而模式,在到今天的重温设计模式。我已经记不清大多数模式的样式了,只记得一系列的设计原则,然后去思考模式的综合。但是工厂模式,至今我仍然难以理解。
写出这篇文章,更多的目的是为了抛砖引玉,希望大家能够多多指教。(初学者选学)
2. 从简单工厂谈起
简单工厂是工厂模式的简化,很多人说他并不是模式,我却不这样认为。相反,我认为简单工厂在某种程度,某些场合上,比起略重量级的工厂模式更为合适。
简单工厂就像一些外包公司,他们没有固定的项目方向,只要给他们一个项目,他们就会去做,而不管这个项目的类型。
让我们先来看看简单工厂的UML图:
3. 简单工厂示例
就写出我们上面UML图的例子了:
class Program
{
static void Main(string[] args)
{
Product product = Factory.CreatePruduct(Type.ProductA);
}
}
enum Type
{
ProductA,
ProductB
}
class Factory
{
public static Product CreatePruduct(Type t)
{
switch (t)
{
case Type.ProductA:
return new ProductA();
break;
case Type.ProductB:
return new ProductB();
break;
}
}
}
abstract class Product
{
}
class ProductA : Product
{
}
class ProductB:Product
{
}
让我们来看下,简单工厂的特点:
他把对象的创建都封装到了一个简单工厂类里,然后让客户端和工厂类发生耦合,而脱离了和具体对象的耦合关系。
优点和缺点暂且不说,这个我们在下文中对比着去看。
4. 进入工厂模式
工厂模式与简单工厂的最大特点就是单一了简单工厂的职责,由外包公司成功转型为行业性软件公司。每个工厂只生产一类产品,这样的优点是取出了简单工厂中丑陋的switch-case或者是if-else。
看下结构图:
我觉得这个图不能很好地表达出工厂方法的意思。让我们先继续向下看:
工厂模式定义一个用户创建对象的接口,让子类去决定实例化哪一个类。
5. 工厂模式实例
让我们来“改造”那个简单工厂的例子:
class Program
{
static void Main(string[] args)
{
Factory factory = new FactoryA();
Product product = factory.CreatePruduct();
}
}
enum Type
{
ProductA,
ProductB
}
abstract class Factory
{
public abstract Product CreatePruduct();
}
class FactoryA : Factory
{
public override Product CreatePruduct()
{
return new ProductA();
}
}
class FactoryB : Factory
{
public override Product CreatePruduct()
{
return new ProductB();
}
}
abstract class Product
{
}
class ProductA : Product
{
}
class ProductB:Product
{
}
让我们来总结下工厂模式的特点:
工厂模式把简单工厂从核心地位给拉了出来,把创建的权利又交还到客户手中,从而让整个项目的核心转移。
6. 无模式的实现
class Program
{
static void Main(string[] args)
{
Product product = new ProductA();
}
}
abstract class Product
{
}
class ProductA : Product
{
}
class ProductB : Product
{
}
7. 工厂方法大比拼
本节是全文的核心,我想也是最容易引起争论的地方,在这里,我只表明我自己的观点,也欢迎大家多提意见!
如果单单从上文的例子来看,使用工厂方法未免有了为了模式而模式之嫌。
假设我们的需求改为了制造一个ProductB的对象。让我们来看看3种模式的大比拼:
<1> 简单工厂模式需要在客户端发生变动:
static void Main(string[] args)
{
Factory factory = new Factory();
//factory.CreatePruduct(Type.ProductA);
factory.CreatePruduct(Type.ProductB);
}
<2> 工厂模式需要在客户端发生变动:
static void Main(string[] args)
{
//Factory f = new FactoryA();
Factory f = new FactoryB();
f.CreatePruduct();
}
<3> 若不用设计模式:
static void Main(string[] args)
{
//Product product = new ProductA();
Product product = new ProductB();
}
三种方式没有什么区别。也就是说,如果在客户端只有一个调用,而且只需要简单的一步初始化,我们完全没有必要为了模式而模式。
接下来,我们假设在客户端存在多个位置需要创造一个Product对象。
<1> 简单工厂模式: 我们需要在每个客户端处都进行相应的修改,传入的参数,没有什么好办法。也就是说我们需要进行N个位置的修改。
<2> 工厂方法模式: 我们可以将Factory的初始化移在单独的一个模块来进行,模拟代码如下:
Factory.dll:
public static Factory CreateFactory()
{
return new FactoryA();
}
Client端:
static void Main(string[] args)
{
Factory factory = CreateFactory();
Product product = factory.CreatePruduct();
}
这样,我们把工厂类单独封装,于是我们在修改的时候只需要对Factory.dll文件进行修改重编译,而不需要在客户端做任何改动。也就是说我们只需要一处的修改。
<3> 无模式的方法:我们的做法依然仿照工厂方法模式的去做,模拟代码如下:
CreateProduct.dll:
public static Product CreateProduct()
{
return new ProductA();
}
Client端:
static void Main(string[] args)
{
Product product = CreateProduct();
}
我们依旧只需要修改CreateProduct.dll,而不需要对任何客户端代码进行修改。也就是说,我们只需要在一处进行修改即可。
这种情况下,当然这也是最常见的情况下,三种模式的PK,简单工厂完败,而我并没有看出工厂模式较之无模式哪里占了上风。
接下来,我们再看最后一种情况,就是增加一套产品ProductC,然后修改为对ProductC的引用。
这里我就不再罗哩罗嗦地粘代码了,直接来说:
<1> 简单工厂需要修改enum条件,然后需要修改Factory中的case条件,需要修改在N个客户端的调用。
<2> 工厂模式仍然是将创造工厂封装成一个dll,然后需要针对产品增加工厂,然后在dll处修改代码。
<3> 无模式情况下只需要增加一个ProductC类,然后修改CreateProduct.dll即可。
结果与上述区别并不大,简单工厂仍然败阵,而无模式较之工厂模式却又简介了很多,而且并没有影响到其扩展性。
那么究竟工厂模式好在哪?他有什么优点?他应该用于何处?
8. 工厂模式,请勿滥用
相比于其他模式,工厂模式是最被滥用的一个。只要有了if---else,就去工厂模式,只要涉及到选择性创造对象,就去工厂模式。似乎工厂模式成了解决一切的万能灵药。
可是让我们看看上面的一些对比,用了工厂模式后,你的代码简单了么?错!恰恰相反,在没有提高代码扩展性的同时,却又增加了类的调用次数,增加了类的个数。这不是我们希望看到的!
9. 反射为工厂而生?
《大话设计模式》上有一句话让我印象颇深,反射反射,程序员的快乐。
更有人说,反射+工厂模式==完美的开放封闭。
还有人说,没有了工厂的反射,就是在乱射一气,反射因为工厂而成为了一项有用的技术。
可是,真的是这样么?
在这里,我就不去写那些耳熟能详的配置文件+反射的代码了。
反射+工厂无非如下:<add key=”Product” value=”ProductA”>。接下来,我们去创造工厂的时候去创造一个[Product]+”Factory”的工厂。然后用工厂去创造产品对象实例。
然而,没有了工厂,反射是在乱反一气么?
让我们看看无模式+反射:配置文件的内容依然如上,接下来,我们只需要在产生产品的CreateProduct.dll中去CreateInstance([Product])即可了。
有什么不好么?至少我没看出来。
总之,反射并非为工厂而生。
10. 工厂究竟用于何处
本节是全文的重点讨论之处,也是难点,是我自己都没太搞明白的知识点。
在这里,我再次声明,我只声明我自己的观点,不敢保证正确与否,初学者慎入!
<1> 很多对象的的创建并不仅仅是一个new那么简单,而是需要很多复杂的步骤,这样,我们如果写成一个工厂,我们就可以根据不同的需求调用不同的工厂,但是如果我们不使用模式,就需要频繁地去改动CreateProduct.dll这个创建产品的类,而这个时候,创建产品不仅仅是修改一个new这样简单,而是需要很多的过程。
这个时候,工厂模式优之很多。但我一时想不出来这样的一个例子,还希望大家指教。
<2> 本条纯属本人扯淡,请选择性参考。我是个思考大于实践的人,心里有好多奇怪的甚至荒谬的想法,但却一直没有机会去VS下,而且这样的念头常常一闪而过,也许是路上,也许是梦里。
我想过用装饰模式或者建造者模式等去综合地构建出一个大对象,而这个对象并非实例化出来,而是由装饰模式和建造者模式构造出来,其实这条和上条很相近,只是我给出了略微具体些的对象细节罢了。他需要一个复杂的创建过程,也就是模式套模式,形成一个模式套。如此下来,这个复杂的创建过程当然要放到工厂中,原因如<1>。
<3> 团队开发中,设计人员往往定义中间层接口,然后交给开发人员去实现,就用Petshop的数据库访问类,如OracleHelper,SQLHelper之类来说话。我的上层调用人员只需要Helper sqlHelper = factory.CreateHelper(“SQL”)去得到SQLHelper,而SQLHelper开发人员只需要继承自Helper抽象类。
相反,如果我们不用工厂模式,上层调用人员就需要Helper helper=new SQLHelper();但是很可能的情况是,他并不知道Helper开发人员要给这个类起什么名字,也许是SQLHelper,也可能是HelperSQL。
这样才叫减小了两层之间的耦合度,增加了项目的透明性,就是这样。
11. 工厂模式一定有工厂么?
其实,模式这个东西我不愿意给他起很多很多的名字,拿出一个东西,知道该怎么去设计最优,这就足够了。
但是在这里,我还是把这个拿出来说一下。工厂模式,一定要有工厂么?
什么是工厂?能创造出产品的就是工厂。软件公司,我们一样可以把他叫做软件工厂,因为他产出了软件。
既然这样,那么我上面的CreateProduct.dll。我也一样可以把他理解为工厂,因为他产出了我需要的产品,就是这么简单。
总之,工厂模式不一定偏要去写***Factory的类,只需要找出一个最好的方法,让这个方法能够根据我们的需求造出我们需要的产品,这就是一个好的工厂。
12. 再谈模式
模式是个什么东西?模式是前人经验的总结,对于某些情况,他们给出了一个他们的意见,但是当情况不符合时,他的模式不见得是最好的模式。
GOF可以创造出23种设计模式,我们每个人的心中也许还都有自己的一套设计模式,也许远远超过23种,只是因为我们不是牛人,所以我们不能把自己的模式起个名字写本书放进去,至此而已。
说到这个,让我想起了我之前的一篇设计模式的文章:重温设计模式(二)——桥接模式(Bridge)。很多人说这不是桥接模式,桥接模式应该怎么怎么样,这个更像什么什么模式。我觉得这是完全没有必要的。
还是那句话,每个人的心中都有自己的一套模式,GOF的模式给我们提供了一个模板,他告诉我们面向对象是可扩展的,他同样告诉我们,设计模式是可扩展的。至于那个是不是桥接模式,我在这里并不想再去讨论,有的人说,这个像装饰模式,装饰模式怎么怎么样,解决了什么什么什么什么。但是,我的这个问题,用这种方法是最容易解决的,那么他对我来说就是一个最好的模式,而这个模式的中心就是组合优于继承,那么我就说,他是一个桥接模式。这本就无可争议。
13. 工厂不工厂
**不**成了我很爱用的一句话,貌似是来源于一首歌中的:简简单单地说,爱是不爱。
爱是不爱,工厂不工厂。To be or not to be , It is a question.
模式本身并不难,难的是我们如何去理解模式;难的是我们如何把模式融会贯通,无刀胜有刀;难的是我们如何在项目中去合理设计,而不是为了模式而模式。
我们究竟是否工厂。下面相当于我对上面我的观点的总结:
1. 工厂越轻量级越好,能不Factory 就尽量不要去Factory。
2. 如果整个项目不那么大,层与层之间对开发者不透明,那么我们就不要去工厂。
3. 如果只是简单类的new创建,那么就不要去工厂。
接下来,让我们的设计师去检查检查自己的设计,是不是多了太多的工厂呢?
14. 疑似广告——学习设计模式的书籍问题
关于设计模式方面的书籍,我看了如下的几本:
<1> 《大话设计模式》 (伍迷)
<2> 《设计模式——可复用的面向对象******》(名字突然想不起来了,GOF)
<3> 《Head first 设计模式》
<4> 《设计模式——基于C#的工程化实现及扩展》
<5> 《Java 与 模式》
剩下就是在博客园上TerryLee以及怪怪的一些高论。
我接下来对上面进行下我的看法总结,冒犯之处,还请见谅。
《大话设计模式》通俗易懂,里面的例子确实很贴近我们的生活,但是他只适合一个入门,并不能在设计模式的路上只看那一本书,正如书中所说,他是为了GOF的书打下基础。这本书,我至少看了五遍,上面了例子,也是敲了四五遍,以至于今后谁说出一种模式,我第一个反映在脑海中的并不是这个模式的内容,而是这个模式的故事。可以说,是这本书打下了我设计模式的基础。
GOF设计模式相对来说很难看懂,也许是因为我C++的确差劲,这本书,也花了我最长的时间,看了两遍。书中的例子很经典,这本书是必看的,我就不在多说。
Head first是我茶余饭后随手翻翻的书,我个人对此书并无好感。
蜡笔小王的设计模式,这本书我认为最适合在看过GOF的书之后看,书上很多的例子以实际工程项目的例子去阐述,让我获益匪浅,建议此书必看!
Java与模式,这书几乎是所有学习Java的人必备的人手一份的书,上面例子的综合让我不得不对作者崇拜至极,但是我觉得也许作者过于强调传统文化,以至于很多模式和原则的讲解有些牵强。
最后是怪怪的文章,也许是我水平有限,怪怪的文章我一向很难理解,以致与一篇文章我一般要看上十遍以上方能看出怪怪想表达出的意思。但是一旦看懂,便是获益匪浅!
15. 个人总结
写到这,文章也该结尾了。终于写完了最让我难以理解的工厂模式,但是在写的过程中,我又想到了很多东西。
最后,总结:工厂模式就是将创建的过程与客户端相分离,而将他们封装在工厂类里。从而避免了客户端与产品类的直接耦合关系。而简单工厂之所以被废弃则是因为他集中了太多的功能,而成为了整个项目的核心,从而违背了单一职责原则。
工厂有优点亦有缺点,工厂不工厂,It’s a question。
理论3
来源于生活
1具体事情做得越多…
1具体事情做得越多,越容易范错误.这每个做过具体工作的人都深有体会,相反,官做得越高,说出的话越抽象越笼统,范错误可能性就越少.好象我们从编程序中也能悟出人生道理
2—早餐买饭
2当我们在买早餐的时候,早餐店里都卖得写什么呢?这点你有注意吗?众多食品摆在那里,你只对营业员说你要何种食品,他便会知道给你拿什么样的食品给你,这说明什么呢?如果用面向对象的思想来理解的话,营业员在这里就充当了一个工厂的角色,他负责根据你的请求返回你需要的食品对象。而这一点正是简单工厂模式的意图。
3
4
实例
1买馒头
从设计模式的类型上来说,简单工厂模式是属于创建型模式,又叫做静态工厂方法(Static Factory Method)模式,但不属于23种GOF设计模式之一。简单工厂模式是由一个工厂对象决定创建出哪一种产品类的实例。简单工厂模式是工厂模式家族中最简单实用的模式,可以理解为是不同工厂模式的一个特殊实现,学习了此模式可以为后面的很多中模式打下基础。
当我们在买早餐的时候,早餐店里都卖得写什么呢?这点你有注意吗?众多食品摆在那里,你只对营业员说你要何种食品,他便会知道给你拿什么样的食品给你,这说明什么呢?如果用面向对象的思想来理解的话,营业员在这里就充当了一个工厂的角色,他负责根据你的请求返回你需要的食品对象。而这一点正是简单工厂模式的意图。
1、首先我们来看看只有一个产品对象的简单工厂模式的实现。其实这很好理解,就当店里只卖一种食品,这里以馒头为例。
1 /// <summary>
2 /// 馒头
3 /// </summary>
4 public class SteamedBread
5 {
6 /// <summary>
7 /// 构造方法
8 /// </summary>
9 public SteamedBread()
10 { }
11
12 /// <summary>
13 /// 销售价格
14 /// </summary>
15 private double price=0.5;
16 public double Price
17 {
18 get { return price; }
19 set { price = value; }
20 }
21 }
OK,产品对象建立好了,下面就是创建工厂(Factory)对象了。
1 /// <summary>
2 /// 工厂角色
3 /// </summary>
4 public class Factory
5 {
6 /// <summary>
7 /// 创建一个馒头(SteamedBread)对象
8 /// </summary>
9 /// <returns></returns>
10 public static SteamedBread CreateInstance()
11 {
12 return new SteamedBread();
13 }
14 }
此时,客户端可以这样来调用:
1 public class Client
2 {
3 public static void Main(string[] args)
4 {
5 //通过工厂创建一个产品的实例
6 SteamedBread sb = Factory.CreateInstance();
7 Console.WriteLine("馒头{0}元一个!", sb.Price);
8 }
9 }
如上就完成了一个简单工厂模式的简单实现,一个产品和一个工厂,工厂负责创建这个产品。但是者种实现有一定的缺陷,为每一种产品创建一个静态方法来完成产品对象的创建,如果有多个产品则需要定义多个静态方法分别返回不同的对象,用设计原则来说的话这样的实现不符合依赖倒置原则(DIP)。如果系统里只有一个单独的产品对象,那么采用这种实现是完全可以的
2、带抽象产品(AbstractProduct)角色的简单工厂模式实现
从上面分析中得知,这种实现得为每一种产品创建一个静态方法来完成产品对象的创建。虽然带有简单工厂的性质,但是又好象不能够完全体现出简单工厂模式的意图。简单工厂的意图是:根据提供给他的数据,返回几个可能类中的一个类的实例。根据意图出发进行分析,要实现完全满足简单工厂模式意图的程序,也就得根据提供给工厂的数据,让工厂根据这个数据来进行判断,然后返回相应的对象。OK,看看是下面这样的吗?
示意性代码如下:
1 /// <summary>
2 /// 食品接口----扮演抽象产品角色
3 /// </summary>
4 public interface IFood
5 {
6 /// <summary>
7 /// 每种食品都有销售价格,这里应该作为共性提升到父类或是接口来
8 /// 由于我们只需要得到价格,所以这里就只提供get属性访问器
9 /// </summary>
10 double price{get;}
11 }
12 ----------------------------------------------------------------------- -------------
13 /// <summary>
14 /// 馒头
15 /// </summary>
16 public class SteamedBread:IFood
17 {
18 /// <summary>
19 /// 构造方法
20 /// </summary>
21 public SteamedBread()
22 { }
23
24 public double price
25 {
26 get
27 {
28 return 0.5;
29 }
30 }
31 }
32 ----------------------------------------------------------------------- -------------
33 /// <summary>
34 /// 包子
35 /// </summary>
36 public class SteamedStuffed:IFood
37 {
38 public SteamedStuffed()
39 { }
40
41 /// <summary>
42 /// 销售价格
43 /// </summary>
44 public double price
45 {
46 get
47 {
48 return 0.6; //0.6元一个
49 }
50 }
51 }
52 ----------------------------------------------------------------------- -------------
53 /// <summary>
54 /// 工厂角色
55 /// </summary>
56 public class Factory
57 {
58 /// <summary>
59 /// 创建一个馒头(SteamedBread)对象
60 /// </summary>
61 /// <returns></returns>
62 public static IFood CreateInstance(string key)
63 {
64 if (key == "馒头")
65 {
66 return new SteamedBread();
67 }
68 else
69 {
70 return new SteamedStuffed();
71 }
72 }
73 }
客户端:
public class Client
76 {
77 public static void Main(string[] args)
78 {
79 //通过工厂创建一个产品的实例
80 IFood food = Factory.CreateInstance("馒头");
81 Console.WriteLine("馒头{0}元一个!", food.price);
82
83 food = Factory.CreateInstance("包子");
84 Console.WriteLine("包子{0}元一个!", food.price);
85 }
86 }
此时的设计就已经完全符合简单工厂模式的意图了。顾客(Client)对早餐店营业员(Factory)说,我要“馒头”,于是营业员便根据顾客所提供的数据(馒头),去众多食品中找,找到了然后就拿给顾客。
3、模式的演变实现
有些情况下Simple Factory可以由抽象产品角色扮演,一个抽象产品类同时是子类的工厂。也就是说,抽象产品角色扮演两种角色和职责,出了基本的定义还还兼任工厂角色的职责,负责产品的创建工作。这里我们在上面的例子基础上适当修改一下OK了,新建立一个抽象类(Evolution):
1 /// <summary>
2 /// 兼任抽象产品角色和工厂角色两种角色
3 /// </summary>
4 public abstract class Evolution
5 {
6 /// <summary>
7 /// 共性字段
8 /// </summary>
9 private double price;
10 public double Price
11 {
12 get { return price; }
13 set { price = value; }
14 }
15
16
17 public static Evolution CreateInstance(string key)
18 {
19 if (key == "馒头")
20 {
21 return new SteamedBread();
22 }
23 else
24 {
25 return new SteamedStuffed();
26 }
}
28 }
那现在,具体的产品对象的设计也应该修改了,把原来实现于IFood接口改为继承此抽象类,如下:
1 public class SteamedBread : Evolution
2 {
3 public SteamedBread()
4 {
5 this.Price = 0.5; //在构造方法里初始话属性的值
6 }
7 }
8
9 public class SteamedStuffed : Evolution
10 {
11 public SteamedStuffed()
12 {
13 this.Price = 0.6;
14 }
15 }
通过上面的演化,此时客户端的调用如下:
1 public class Client
2 {
3 public static void Main(string[] args)
4 {
5 Evolution el = Evolution.CreateInstance("包子");
6 Console.WriteLine("包子{0}元一个!", el.Price);
7 }
8 }
2 一个整体思路—示例
无模式的实现
class Program
{
static void Main(string[] args)
{
Product product = new ProductA();
}
}
abstract class Product
{
}
class ProductA : Product
{
}
class ProductB : Product
{
}
class Program
{
static void Main(string[] args)
{
Product product = Factory.CreatePruduct(Type.ProductA);
}
}
enum Type
{
ProductA,
ProductB
}
class Factory
{
public static Product CreatePruduct(Type t)
{
switch (t)
{
case Type.ProductA:
return new ProductA();
break;
case Type.ProductB:
return new ProductB();
break;
}
}
}
abstract class Product
{
}
class ProductA : Product
{
}
class ProductB:Product
{
}
工厂模式与简单工厂的最大特点就是单一了简单工厂的职责,由外包公司成功转型为行业性软件公司。每个工厂只生产一类产品,这样的优点是取出了简单工厂中丑陋的switch- case或者是if-else。
让我们来“改造”那个简单工厂的例子:
class Program
{
static void Main(string[] args)
{
Factory factory = new FactoryA();
Product product = factory.CreatePruduct();
}
}
enum Type
{
ProductA,
ProductB
}
abstract class Factory
{
public abstract Product CreatePruduct();
}
class FactoryA : Factory
{
public override Product CreatePruduct()
{
return new ProductA();
}
}
class FactoryB : Factory
{
public override Product CreatePruduct()
{
return new ProductB();
}
}
abstract class Product
{
}
class ProductA : Product
{
}
class ProductB:Product
{
}
让我们来总结下工厂模式的特点:
工厂模式把简单工厂从核心地位给拉了出来,把创建的权利又交还到客户手中,从而让整个项目的核心转移。
3反射加工厂模式
反射基础
反射(Reflection)是.net中的重要机制,通过放射,可以在运行时获得.NET中每一个类型(包括类、结构、委托、接口和枚举等)的成员,包括方法、属性、事件,以及构造函数等。还可以获得每个成员的名称、限定符和参数等。有了反射,即可对每一个类型了如指掌。如果获得了构造函数的信息,即可直接创建对象,即使这个对象的类型在编译时还不知道。 欢迎访问米鼠网
1、.NET可执行应用程序结构
程序代码在编译后生成可执行的应用,我们首先要了解这种可执行应用程序的结构。
应用程序结构分为应用程序域―程序集―模块―类型―成员几个层次,公共语言运行库加载器管理应用程序域,这种管理包括将每个程序集加载到相应的应用程序域以及控制每个程序集中类型层次结构的内存布局。
程序集包含模块,而模块包含类型,类型又包含成员,反射则提供了封装程序集、模块和类型的对象。我们可以使用反射动态地创建类型的实例,将类型绑定到现有对象或从现有对象中获取类型,然后调用类型的方法或访问其字段和属性。反射通常具有以下用途。
(1)使用Assembly定义和加载程序集,加载在程序集清单中列出模块,以及从此程序集中查找类型并创建该类型的实例。
(2)使用Module了解包含模块的程序集以及模块中的类等,还可以获取在模块上定义的所有全局方法或其他特定的非全局方法。
(3)使用ConstructorInfo了解构造函数的名称、参数、访问修饰符(如pulic 或private)和实现详细信息(如abstract或virtual)等。使用Type的GetConstructors或GetConstructor方法来调用特定的构造函数。
(4)使用MethodInfo了解方法的名称、返回类型、参数、访问修饰符(如pulic 或private)和实现详细信息(如abstract或virtual)等。使用Type的GetMethods或GetMethod方法来调用特定的方法。
(5)使用FiedInfo了解字段的名称、访问修饰符(如public或private)和实现详细信息(如static)等,并获取或设置字段值。
(6)使用EventInfo了解事件的名称、事件处理程序数据类型、自定义属性、声明类型和反射类型等,添加或移除事件处理程序。
(7)使用PropertyInfo了解属性的名称、数据类型、声明类型、反射类型和只读或可写状态等,获取或设置属性值。
(8)使用ParameterInfo了解参数的名称、数据类型、是输入参数还是输出参数,以及参数在方法签名中的位置等。
System.Reflection.Emit命名空间的类提供了一种特殊形式的反射,可以在运行时构造类型。
反射也可用于创建称为类型浏览器的应用程序,使用户能够选择类型,然后查看有关选定类型的信息。
此外,Jscript等语言编译器使用反射来构造符号表。System.Runtime.Serialization命名空间中的类使用反射来访问数据并确定要永久保存的字段,System.Runtime.Remoting命名空间中的类通过序列化来间接地使用反射。
3、在设计模式实现中使用反射技术
采用反射技术可以简化工厂的实现。
(1)工厂方法:通过反射可以将需要实现的子类名称传递给工厂方法,这样无须在子类中实现类的实例化。
(2)抽象工厂:使用反射可以减少抽象工厂的子类。
采用反射技术可以简化工厂代码的复杂程度,在.NET项目中,采用反射技术的工厂已经基本代替了工厂方法。
采用反射技术可以极大地简化对象的生成,对以下设计模式的实现也有很大影响。
(1)命令模式:可以采用命令的类型名称作为参数直接获得命令的实例,并且可以动态执行命令。
(2)享元模式:采用反射技术实例化享元可以简化享元工厂。
反射+工厂无非如下:<add key=”Product” value=”ProductA”>。
例子:
using System;
namespace Factory
{
public interface IFruit
{
}
public class Orange : IFruit
{
public Orange()
{
Console.WriteLine("An orange is got!");
}
}
public class Apple : IFruit
{
public Apple()
{
Console.WriteLine("An apple is got!");
}
}
public class FruitFactory
{
private static FruitFactory _instance = new FruitFactory();
public static FruitFactory Instance
{
get { return _instance; }
}
public IFruit MakeFruit(string Name)
{
IFruit MyFruit = null;
try
{
Type type = Type.GetType(Name, true);
MyFruit = (IFruit)Activator.CreateInstance(type);
}
catch (TypeLoadException e)
{
Console.WriteLine("I dont know this kind of fruit,exception caught - {0}", e.Message);
}
return MyFruit;
}
}
public class Test
{
static void Main()
{
string FruitName = Console.ReadLine();
FruitFactory.Instance.MakeFruit("Factory." + FruitName);
}
}
}
运行:
输入: Apple 查看结果
一个综合点的例子
1. 简述
通过前面的学习,我们以传统的方式实现了简单工厂,工厂方法和抽象工厂,但是有些场合下如此处理,代码会变得冗余并且难以维护。假设我们要创建交通工具。可以是汽车,火车,轮船等,其结构如下:
我们可以采用简单工厂,通过参数指示创建所需要的对象类型。如果要增加子类,例如卡车和轿车,则必须增加参数和相应的代码。如果子类层次过多,则会是程序变得很难维护。
但我们可以采用工厂方法模式来实现,即定义一个产生交通工具的接口,然后在子类中实现创建具体子类。代码如下:
C#代码
public interface ICreateVehicle
{
Vehicle CreateCehicle();
}
public abstract class Vehicle
{
}
public class Car:Vehicle
{
public Car()
{
Console.WriteLine("创建了一个Car");
}
}
public class Boat:Vehicle
{
public Boat()
{
Console.WriteLine("创建了一个Boat");
}
}
public class CreateCar:ICreateVehicle
{
ICreateVehicle 成员#region ICreateVehicle 成员
public Vehicle CreateCehicle()
{
Vehicle vehicle=new Car();
return vehicle;
}
#endregion
}
public class CreateBoat:ICreateVehicle
{
ICreateVehicle 成员#region ICreateVehicle 成员
public Vehicle CreateCehicle()
{
Vehicle vehicle = new Boat();
Console.WriteLine("Car");
return vehicle;
}
#endregion
}
public interface ICreateVehicle
{
Vehicle CreateCehicle();
}
public abstract class Vehicle
{
}
public class Car:Vehicle
{
public Car()
{
Console.WriteLine("创建了一个Car");
}
}
public class Boat:Vehicle
{
public Boat()
{
Console.WriteLine("创建了一个Boat");
}
}
public class CreateCar:ICreateVehicle
{
ICreateVehicle 成员#region ICreateVehicle 成员
public Vehicle CreateCehicle()
{
Vehicle vehicle=new Car();
return vehicle;
}
#endregion
}
public class CreateBoat:ICreateVehicle
{
ICreateVehicle 成员#region ICreateVehicle 成员
public Vehicle CreateCehicle()
{
Vehicle vehicle = new Boat();
Console.WriteLine("Car");
return vehicle;
}
#endregion
}
这就是工厂方法。如果希望增加新的交通工具,不仅需要实现交通工具接口,还需要实现生产交通工具的工厂方法。
显然我们需要几十种交通工具,则需要几十个具体的工厂。而这些类的区别仅仅是返回相对应的类的实例,所以位维护带来了很大的麻烦。如果需要在接口中增加一个带参数的方法,则所有的子类都需要修改。
在这种场合下,采用抽象工厂与工厂方法没有区别,因为这里并不涉及产品线,抽象工厂并不能解决其中的问题,如果每种交通工具都要有对应的车站,则要使用抽象工厂,但是将会跟复杂。
有没有可能将需要创建类的类型传递到工厂方法中,由工厂方法根据类型返回相应的实例?解决这个问题的关键是需要动态的决定需要创建的类,这不是设计模式能解决的问题,属于软件平台的功能范畴。.NET可以提供反射技术。
我们先看通过反射技术实现的简化的工厂,代码如下:
C#代码
public class CreateVehicleByType:ICreateVehicle
{
ICreateVehicle 成员#region ICreateVehicle 成员
private Type VehicleTYpe;
public CreateVehicleByType(string strType)
{
Type t = Type.GetType(strType);
VehicleTYpe = t;
}
public Vehicle CreateCehicle()
{
ConstructorInfo objConstrutor = VehicleTYpe.GetConstructor(System.Type.EmptyTypes);
Vehicle c = (Vehicle)objConstrutor.Invoke(null);
return c;
}
#endregion
}
public class CreateVehicleByType:ICreateVehicle
{
ICreateVehicle 成员#region ICreateVehicle 成员
private Type VehicleTYpe;
public CreateVehicleByType(string strType)
{
Type t = Type.GetType(strType);
VehicleTYpe = t;
}
public Vehicle CreateCehicle()
{
ConstructorInfo objConstrutor = VehicleTYpe.GetConstructor(System.Type.EmptyTypes);
Vehicle c = (Vehicle)objConstrutor.Invoke(null);
return c;
}
#endregion
}
在使用是,只要在创建时带入需要创建的类的类型:
C#代码
static void Main(string[] args)
{
string strType = "Car";
Vehicle v;
ICreateVehicle f = null;
if (strType == "Car")
{
f = new CreateVehicleByType("FactoryVehicle.Car");
}
else if (strType == "Boat")
{
f = new CreateVehicleByType("Boat");
}
v = f.CreateCehicle();
Console.ReadLine();
}
通过反射技术,我们将很多的具体的工厂类简化为一个类,并且新增加类型时不需要新的工厂类,这样我们得到简化的工厂,可以称其为“反射工厂”。
先来看看反射技术的基本格式:
Assembly.Load(“程序集名称”).CreateInstance(“命名空间.类名称”);
只要在程序顶端写上using System.Reflection来引用Reflection,就可以采用反射工厂来克服抽象工厂模式的先天不足
2.实例
先来看看,大话设计模式中的利用反射加抽象工厂的数据访问程序。先来看看反射技术的基本格式:
Assembly.Load(“程序集名称”).CreateInstance(“命名空间.类名称”);
只要在程序顶端写上using System.Reflection来引用Reflection,就可以采用反射工厂来克服抽象工厂模式的先天不足。下面我们来看通过反射技术实现不同数据库的访问程序.
先来看结构图:
DataAccess类,用反射技术,取代了抽象工厂中的IFactory,SqlServerFactory和AccessFactory。
具体代码:
C#代码
public class User
{
private int _id;
public int ID
{
get { return _id; }
set { _id = value; }
}
private string _name;
public string Name
{
get { return _name; }
set { _name = value; }
}
}
public class Department
{
private int _id;
public int ID
{
get { return _id; }
set { _id = value; }
}
private string _deptName;
public string DeptName
{
get { return _deptName; }
set { _deptName = value; }
}
}
public interface IUser
{
void Insert(User user);
User GetUser(int id);
}
public class SqlserverUser : IUser
{
public void Insert(User user)
{
Console.WriteLine("在Sqlserver中给User表增加一条记录");
}
public User GetUser(int id)
{
Console.WriteLine("在Sqlserver中根据ID得到User表一条记录");
return null;
}
}
public class AccessUser : IUser
{
public void Insert(User user)
{
Console.WriteLine("在Access中给User表增加一条记录");
}
public User GetUser(int id)
{
Console.WriteLine("在Access中根据ID得到User表一条记录");
return null;
}
}
public interface IDepartment
{
void Insert(Department department);
Department GetDepartment(int id);
}
public class SqlserverDepartment : IDepartment
{
public void Insert(Department department)
{
Console.WriteLine("在Sqlserver中给Department表增加一条记录");
}
public Department GetDepartment(int id)
{
Console.WriteLine("在Sqlserver中根据ID得到Department表一条记录");
return null;
}
}
public class AccessDepartment : IDepartment
{
public void Insert(Department department)
{
Console.WriteLine("在Access中给Department表增加一条记录");
}
public Department GetDepartment(int id)
{
Console.WriteLine("在Access中根据ID得到Department表一条记录");
return null;
}
}
public class DataAccess
{
private static readonly string AssemblyName = "抽象工厂模式";
private static readonly string db = "Sqlserver";
//private static readonly string db = "Access";
public static IUser CreateUser()
{
string className = AssemblyName + "." + db + "User";
return (IUser)Assembly.Load(AssemblyName).CreateInstance(className);
}
public static IDepartment CreateDepartment()
{
string className = AssemblyName + "." + db + "Department";
return (IDepartment)Assembly.Load(AssemblyName).CreateInstance(className);
}
}
public class User
{
private int _id;
public int ID
{
get { return _id; }
set { _id = value; }
}
private string _name;
public string Name
{
get { return _name; }
set { _name = value; }
}
}
public class Department
{
private int _id;
public int ID
{
get { return _id; }
set { _id = value; }
}
private string _deptName;
public string DeptName
{
get { return _deptName; }
set { _deptName = value; }
}
}
public interface IUser
{
void Insert(User user);
User GetUser(int id);
}
public class SqlserverUser : IUser
{
public void Insert(User user)
{
Console.WriteLine("在Sqlserver中给User表增加一条记录");
}
public User GetUser(int id)
{
Console.WriteLine("在Sqlserver中根据ID得到User表一条记录");
return null;
}
}
public class AccessUser : IUser
{
public void Insert(User user)
{
Console.WriteLine("在Access中给User表增加一条记录");
}
public User GetUser(int id)
{
Console.WriteLine("在Access中根据ID得到User表一条记录");
return null;
}
}
public interface IDepartment
{
void Insert(Department department);
Department GetDepartment(int id);
}
public class SqlserverDepartment : IDepartment
{
public void Insert(Department department)
{
Console.WriteLine("在Sqlserver中给Department表增加一条记录");
}
public Department GetDepartment(int id)
{
Console.WriteLine("在Sqlserver中根据ID得到Department表一条记录");
return null;
}
}
public class AccessDepartment : IDepartment
{
public void Insert(Department department)
{
Console.WriteLine("在Access中给Department表增加一条记录");
}
public Department GetDepartment(int id)
{
Console.WriteLine("在Access中根据ID得到Department表一条记录");
return null;
}
}
public class DataAccess
{
private static readonly string AssemblyName = "抽象工厂模式";
private static readonly string db = "Sqlserver";
//private static readonly string db = "Access";
public static IUser CreateUser()
{
string className = AssemblyName + "." + db + "User";
return (IUser)Assembly.Load(AssemblyName).CreateInstance(className);
}
public static IDepartment CreateDepartment()
{
string className = AssemblyName + "." + db + "Department";
return (IDepartment)Assembly.Load(AssemblyName).CreateInstance(className);
}
}
调用代码:
C#代码
User user = new User();
Department dept = new Department();
IUser iu = DataAccess.CreateUser();
iu.Insert(user);
iu.GetUser(1);
IDepartment id = DataAccess.CreateDepartment();
id.Insert(dept);
id.GetDepartment(1);
Console.Read();
}
User user = new User();
Department dept = new Department();
IUser iu = DataAccess.CreateUser();
iu.Insert(user);
iu.GetUser(1);
IDepartment id = DataAccess.CreateDepartment();
id.Insert(dept);
id.GetDepartment(1);
Console.Read();
}
现在我们要增加Oracle数据访问,相关类的增加是不可避免的,这点是无论我们用什么方法都解决不了的,这是扩展,依照开发-封闭原则,对于扩展,我们开放,但对与修改我们关闭。就现在的代码中,我们要换Oracle很容易,只需将db=”Sqlserver”换成db=”Oracle”。
现在我们需要增加Product,只需增加三个与Product相关的类,再修改一下DataAccess,在其中增加一个创建Product的方法就可以了。
现在我们要更换数据访问程序是,我们还需要修改程序,重新编译,我们可以利用配置文件来解决这个问题,首先要在我们的项目中添加config文件,内容如下:
C#代码
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
<add key="DB" value="Sqlserver"/>
</appSettings>
</configuration>
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
<add key="DB" value="Sqlserver"/>
</appSettings>
</configuration>
再在项目中引用System.configuration,并在程序头增加using System.configuration;, 然后修改DataAccess类的字段db的赋值代码:
C#代码
class DataAccess
{
private static readonly string AssemblyName = "抽象工厂模式";
private static readonly string db = ConfigurationManager.AppSettings["DB"];
public static IUser CreateUser()
{
string className = AssemblyName + "." + db + "User";
return (IUser)Assembly.Load(AssemblyName).CreateInstance(className);
}
public static IDepartment CreateDepartment()
{
string className = AssemblyName + "." + db + "Department";
return (IDepartment)Assembly.Load(AssemblyName).CreateInstance(className);
}
}
class DataAccess
{
private static readonly string AssemblyName = "抽象工厂模式";
private static readonly string db = ConfigurationManager.AppSettings["DB"];
public static IUser CreateUser()
{
string className = AssemblyName + "." + db + "User";
return (IUser)Assembly.Load(AssemblyName).CreateInstance(className);
}
public static IDepartment CreateDepartment()
{
string className = AssemblyName + "." + db + "Department";
return (IDepartment)Assembly.Load(AssemblyName).CreateInstance(className);
}
}
3.总结
使用反射工厂的优点是极大的减少了工厂类的数量,降低了代码的冗余,并且系统更容易扩展,增加新类型后,不需要修改工厂类。
使用反射工厂的代价是工厂与产品之间的依赖关系不明显,由于动态绑定,因此理论上可以用一个工厂完成很多类型的实例化,从而使得代码不容易理解。另外就是增加了测试难度,因为创建是动态完成的。
采用反射技术创建的反射工厂可以使系统更灵活,使工厂和产品之间的依赖关系更小。在.NET的项目中大量的使用了反射工厂取代的传统的工厂。
反射在设计模式中的应用
利用设计模式可以使我们的代码更灵活,更容易扩展,更容易维护。各种面向对象的程序设计语言都提供了基本相同的机制:比如类、继承、派生、多态等等。但是又有各自的特色,c# 中的反射机制便是一个很重要的工具,好好地利用就可以在实际中发挥很大的作用。
我们来看一个例子:
我的程序中有需要一系列的对象,比如apple,orange…, 要想利用他们,我们就必须在程序中根据用户要求,然后一个个调用 new 操作符来生成他们,这样客户程序就要知道相应的类的信息,生成的代码显然不够灵活。我们可以在代码中不利用具体的类,而只是说明我们需要什么,然后就能够得到我们想要的对象吗?
哦,我们都看设计模式,听吧,很多人都在那里鼓吹他们是如何如何的棒,我们看看怎么样利用他们来解决问题。目标明确了,那我们看看哪个能够符合我们的要求。GoF的《设计模式》都看过吧,似懂非懂的看了一些,那我们看看能够不能够“凑”上去呢?J 嗯,我们的程序考虑的是对象怎么创建的,创建型模式应该符合要求吧。然后我们浏览一下各模式的“意图”部分。呵呵,第一个好像就撞到彩了,抽象工厂,我们看看吧,“提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类”,至少“无需指定它们具体的类”符合我们的要求。来看看它的结构吧:
我们的问题好像用不到这么复杂吧,只有orange,apple等等(应该就是product了),他们显然是一类的,都是fruit,我们只要一个生产水果的工厂就可以,左边的继承层次不要,只有一个FruitFactroy看看行不,先别管它正统不正统,实用就行J
下面的一些东西显然是我们需要的:
Public interface IFruit
{
}
public class Orange:IFruit
{
public Orange()
{
Console.WriteLine("An orange is got!");
}
}
public class Apple:IFruit
{
public Apple()
{
Console.WriteLine("An apple is got!");
}
}
我们的FruitFactory应该是怎么样呢?上面的结构图中它给的是CreateProductA,那好,我就MakeOrange,还有一个CreateProductB,俺MakeOrange还不行??
public class FruitFactory
{
public Orange MakeOrange()
{
return new Orange();
}
public Apple MakeApple()
{
return new Apple();
}
}
怎么使用这个工厂呢?我们来写下面的代码:
string FruitName = Console.ReadLine();
IFruit MyFruit = null;
FruitFactory MyFruitFactory = new FruitFactory();
switch (FruitName)
{
case "Orange":
MyFruit = MyFruitFactory.MakeOrange();
break;
case "Apple":
MyFruit = MyFruitFactory.MakeApple();
break;
default:
break;
}
编译运行,然后在控制台输入想要的东西,呵呵,成功了。沉浸在幸福中的你得意忘形了吧。
不过等等,它好像还不完美,我如果想要pear,我既要在客户代码中的switch中加入判断,又要在工厂方法中加入MakePear方法,好像不怎么优雅。更好一点,在工厂中只提供一个方法,MakeFruit,然后传递进一个参数Name,代表我们想要的水果的名称,这样的话,似乎我们的客户代码中的那个switch就可以不要了,相反,在FruitFactory中好像需要一个,还等什么呢?实现吧。
FruitFactory:
public class FruitFactory
{
public IFruit MakeFruit(string Name)
{
switch (Name)
{
case "Orange":
return new Orange();
case "Apple":
return new Apple();
default:
return null;
}
}
}
客户代码:
string FruitName = Console.ReadLine();
IFruit MyFruit;
FruitFactory MyFruitFactory = new FruitFactory();
MyFruit = MyFruitFactory.MakeFruit(FruitName);
这样看起来好多了,至少我客户代码中不要再写那么一长串的判断代码了。
阿Q精神又在起作用,我们又沉浸在成功的喜悦中了。 嗯,代码好像可以,应该没有什么改进了。但是好像又有另外一个声音在说:
“除了一点……”
“嗯? 等等,什么?”
“FruitFactory也有switch啊,看起来也ugly啊!”
“哼,肯定是看《重构》或者是《TDD》了,怎么要求那么苛刻!反正闲着也是闲着,看看可以改不?”
既然不要条件判断,传入的只有水果的名称,假如Name = “Apple”,要生成一个Apple的对象,我需要new Apple(),如果我能够这样多好: new MakeItToClass(Name),把字符串转换成一个类。c#中虽然没有上述语法,但是提供了相应的机制,那就是反射。其中一个重要的类就是System.Type类,它对于反射起着核心的作用。我们可以使用 Type 对象的方法、字段、属性和嵌套类来查找有关该类型的所有信息。
另外一个重要的类就是System.Activator,它包含特定的方法,用以在本地或从远程创建对象类型,或获取对现有远程对象的引用。
我们可以先利用Type类获取Name指定的类名的类的Type信息,然后可以根据这个信息利用Activator创建对象。还等什么呢?
public class FruitFactory
{
public IFruit MakeFruit(string Name)
{
IFruit MyFruit = null;
try
{
Type type = Type.GetType(Name,true);
MyFruit = (IFruit)Activator.CreateInstance(type);
}
catch (TypeLoadException e)
Console.WriteLine("I dont know this kind of fruit,exception caught - {0}" ,e.Message);
return MyFruit;
}
}
经过这样的处理以后,增加新的水果的时候,我们不需要修改客户代码了,同时工厂的代码也不需要修改了,怎么样,爽吧!