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

C#之三十八 简单工厂设计模式

2016-06-01 20:22 561 查看
设计模式(Design
pattern)是一套被反复使用、多数人知晓的、经过分编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性。

从设计模式的类型上来说,简单工厂模式是属于创建型模式,又叫做静态工厂方法(Static Factory Method)模式,但不属于23种GOF设计模式之一。简单工厂模式是由一个工厂对象决定创建出哪一种产品类的实例。简单工厂模式是工厂模式家族中最简单实用的模式,可以理解为是不同工厂模式的一个特殊实现,学习了此模式可以为后面的很多中模式打下基础。

4.1 设计模式概述

设计模式(Design
pattern)是一套被反复使用、多数人知晓的、经过分编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性。

毫无疑问,设计模式于己于他人于系统都是多赢的,设计模式使代码编制真正工程化,设计模式是软件工程的基石,如同大厦的一块块砖石一样。

GoF的“设计模式”是第一次将设计模式提升到理论高度,并将之规范化,本书提出了23种基本设计模式,自此,在可复用面向对象软件的发展过程中,新的大量的设计模式不断出现。

4.2、设计模式和框架

现在,可复用面向对象软件系统现在一般划分为三大类:应用程序工具箱和框架(Framework),我们平时开发的具体软件都是应用程序;Java的API属于工具箱;而框架是构成一类特定软件可复用设计的一组相互协作的类。EJB(EnterpriseJavaBeans)是Java应用于企业计算的框架.

框架通常定义了应用体系的整体结构类和对象的关系等等设计参数,以便于具体应用实现者能集中精力于应用本身的特定细节。框架主要记录软件应用中共同的设计决策,框架强调设计复用,因此框架设计中必然要使用设计模式.

另外,设计模式有助于对框架结构的理解,成熟的框架通常使用了多种设计模式,如果你熟悉这些设计模式,毫无疑问,你将迅速掌握框架的结构,我们一般开发者如果突然接触EJBJ2EE等框架,会觉得特别难学,难掌握,那么转而先掌握设计模式,无疑是给了你剖析EJB或J2EE系统的一把利器。

4.3、设计模式的原则

近年来,大家都开始注意设计模式。那么,到底我们为什么要用设计模式呢?这么多设计模式为什么要这么设计呢?说实话,以前我还真没搞清楚。就是看大家一口一个"Design

pattern",心就有点发虚。于是就买了本"四人帮"的设计模式,结果看得似懂非懂:看得时候好像是懂了,过一会就忘了。可能是本人比较"愚钝"吧:))最近,有了点感悟。"独乐不如众乐",与大家分享一下,还望指教!

为什么要提倡"Design Pattern"呢?根本原因是为了代码复用,增加可维护性。那么怎么才能实现代码复用呢?OO界有前辈的几个原则:"开-闭"原则(Open Closed Principal)、里氏代换原则、合成复用原则。设计模式就是实现了这些原则,从而达到了代码复用、增加可维护性的目的。

4.3.1、"开放-封闭"原则

此原则是由"Bertrand Meyer"提出的。原文是:"Software entitiesshould be open for extension,but closed for modification"。就是说模块应对扩展开放,而对修改关闭。模块应尽量在不修改原(是"原",指原来的代码)代码的情况下进行扩展。那么怎么扩展呢?我们看工厂模式"factory pattern":假设中关村有一个卖盗版盘和毛片的小子,我们给他设计一"光盘销售管理软件"。我们应该先设计一"光盘"接口。如图:

[pre]______________

|<>|

| 光盘 |

|_____________|

|+卖() |

| |

|_____________|[/pre]

而盗版盘和毛片是其子类。小子通过"DiscFactory"来管理这些光盘。代码为:

public
class DiscFactory{

public static 光盘getDisc(java/lang/String.java.html" target="_blank">String name){

return (光盘)java/lang/Class.java.html"target="_blank">Class.forName(name).getInstance();

}

}

有人要买盗版盘,怎么实现呢?

public class 小子{

public static void main(java/lang/String.java.html"target="_blank">String[] args){

光盘 d=DiscFactory.getDisc("盗版盘");

光盘.卖();

}

}

如果有一天,这小子良心发现了,开始卖正版软件。没关系,我们只要再创建一个"光盘"的子类"正版软件"就可以了。不需要修改原结构和代码。怎么样?对扩展开发,对修改关闭。"开-闭原则"

工厂模式是对具体产品进行扩展,有的项目可能需要更多的扩展性,要对这个"工厂"也进行扩展,那就成了"抽象工厂模式"。

4.3.2、里氏代换原则

里氏代换原则是由"Barbara Liskov"提出的。如果调用的是父类的话,那么换成子类也完全可以运行。比如:

光盘 d=new 盗版盘();

d.卖();

现在要将"盗版盘"类改为"毛片"类,没问题,完全可以运行。Java编译程序会检查程序是否符合里氏代换原则。还记得java继承的一个原则吗?子类overload方法的访问权限不能小于父类对应方法的访问权限。比如"光盘"中的方法"卖"访问权限是"public",那么"盗版盘"和"毛片"中的"卖"方法就不能是package或private,编译不能通过。为什么要这样呢?你想啊:如果"盗版盘"的"卖"方法是private。那么下面这段代码就不能执行了:

光盘 d=new 盗版盘();

d.卖();

可以说:里氏代换原则是继承复用的一个基础。

4.3.3、合成复用原则

就是说要少用继承,多用合成关系来实现。我曾经这样写过程序:有几个类要与数据库打交道,就写了一个数据库操作的类,然后别的跟数据库打交道的类都继承这个。结果后来,我修改了数据库操作类的一个方法,各个类都需要改动。"牵一发而动全身"!面向对象是要把波动限制在尽量小的范围。

在Java中,应尽量针对Interface编程,而非实现类。这样,更换子类不会影响调用它方法的代码。要让各个类尽可能少的跟别人联系,"不要与陌生人说话"。这样,城门失火,才不至于殃及池鱼。扩展性和维护性才能提高

理解了这些原则,再看设计模式,只是在具体问题上怎么实现这些原则而已。张无忌学太极拳,忘记了所有招式,打倒了"玄幂二老",所谓"心中无招"。设计模式可谓招数,如果先学通了各种模式,又忘掉了所有模式而随心所欲,可谓OO之最高境界。呵呵,搞笑,搞笑!(JR)

4.3.4依赖倒转原则

抽象不应该依赖与细节,细节应当依赖与抽象。

要针对接口编程,而不是针对实现编程。

传递参数,或者在组合聚合关系中,尽量引用层次高的类。

主要是在构造对象时可以动态的创建各种具体对象,当然如果一些具体类比较稳定,就不必在弄一个抽象类做它的父类,这样有画舌添足的感觉

4.3.5接口隔离原则

定制服务的例子,每一个接口应该是一种角色,不多不少,不干不该干的事,该干的事都要干

4.3.6抽象类

抽象类不会有实例,一般作为父类为子类继承,一般包含这个系的共同属性和方法。

注意:好的继承关系中,只有叶节点是具体类,其他节点应该都是抽象类,也就是说具体类

是不被继承的。将尽可能多的共同代码放到抽象类中。

4.3.7迪米特法则

最少知识原则。不要和陌生人说话。

4.4、一个模式的四个基本要素

设计模式使人们可以更加简单方便地复用成功的设计和体系结构。将已证实的技术表述成设计模式也会使新系统开发者更加容易理解其设计思路。

4.4.[b]1. 模式名称(patternname)[/b]

一个助记名,它用一两个词来描述模式的问题、解决方案和效果。命名一个新的模式增加了我们的设计词汇。设计模式允许我们在较高的抽象层次上进行设计。基于一个模式词汇表,我们自己以及同事之间就可以讨论模式并在编写文档时使用它们。模式名可以帮助我们思考,便于我们与其他人交流设计思想及设计结果。找到恰当的模式名也是我们设计模式编目工作的难点之一。

4.4.2. 问题(problem)

描述了应该在何时使用模式。它解释了设计问题和问题存在的前因后果,它可能描述了特定的设计问题,如怎样用对象表示算法等。也可能描述了导致不灵活设计的类或对象结构。有时候,问题部分会包括使用模式必须满足的一系列先决条件。

4.4.3. 解决方案(solution)

描述了设计的组成成分,它们之间的相互关系及各自的职责和协作方式。因为模式就像一个模板,可应用于多种不同场合,所以解决方案并不描述一个特定而具体的设计或实现,而是提供设计问题的抽象描述和怎样用一个具有一般意义的元素组合(类或对象组合)来解决这个问题。

4.4.4. 效果(consequences)

描述了模式应用的效果及使用模式应权衡的问题。尽管我们描述设计决策时,并不总提到模式效果,但它们对于评价设计选择和理解使用模式的代价及好处具有重要意义。软件效果大多关注对时间和空间的衡量,它们也表述了语言和实现问题。因为复用是面向对象设计的要素之一,所以模式效果包括它对系统的灵活性、扩充性或可移植性的影响,显式地列出这些效果对理解和评价这些模式很有帮助。

4.5、模式概述

从设计模式的类型上来说,简单工厂模式是属于创建型模式,又叫做静态工厂方法(StaticFactory Method)模式,但不属于23种GOF设计模式之一。简单工厂模式是由一个工厂对象决定创建出哪一种产品类的实例。简单工厂模式是工厂模式家族中最简单实用的模式,可以理解为是不同工厂模式的一个特殊实现,学习了此模式可以为后面的很多中模式打下基础。那好,我们就来了解下什么是简单工厂模式?

我们来分析一个现实生活中的案例,每天早晨起床洗唰后是干什么呢?吃早餐(这里只针对在外吃早餐的上班族)、坐车(我是穷人只有坐公交,当然很多有钱人都自己有车,这里不考虑这些)去公司上班、是这样的吗?OK,下面就来分析下吃早餐中的故事,大家先看看下面这个图:



当我们在买早餐的时候,早餐店里都卖得写什么呢?这点你有注意吗?众多食品摆在那里,你只对营业员说你要何种食品,他便会知道给你拿什么样的食品给你,这说明什么呢?如果用面向对象的思想来理解的话,营业员在这里就充当了一个工厂的角色,他负责根据你的请求返回你需要的食品对象。而这一点正是简单工厂模式的意图。

4.6、模式意图

简单工厂模式根据提供给他的数据,返回几个可能类中的一个类的实例。

4.7、模式UML图

下面是简单工厂模式的示意性UML图:



4.8、模式参与者

工厂(Factory)角色:接受客户端的请求,通过请求负责创建相应的产品对象。

抽象产品(AbstractProduct)角色: 是工厂模式所创建对象的父类或是共同拥有的接口。可是抽象类或接口。

具体产品(ConcreteProduct)对象:工厂模式所创建的对象都是这个角色的实例。

4.9、模式实现

我们通过上面的分析,已经清晰的知道了工厂模式中的各种角色和职责,那工厂模式通过代码是怎么实现的呢?OK,下面将继续分析上面的吃早餐中的故事,做一个简单的示例实现。

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)。如果系统里只有一个单独的产品对象,那么采用这种实现是完全可以的。UML图如下:



2、带抽象产品(AbstractProduct)角色的简单工厂模式实现

从上面分析中得知,这种实现得为每一种产品创建一个静态方法来完成产品对象的创建。虽然带有简单工厂的性质,但是又好象不能够完全体现出简单工厂模式的意图。简单工厂的意图是:根据提供给他的数据,返回几个可能类中的一个类的实例。根据意图出发进行分析,要实现完全满足简单工厂模式意图的程序,也就得根据提供给工厂的数据,让工厂根据这个数据来进行判断,然后返回相应的对象。OK,看看是下面这样的吗?



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 }

74 ------------------------------------------------------------------------------------

75 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 }

27 }

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 }

UML草图如下:



如果抽象产品角色省略,那么工厂角色就可以与具体产品角色合并。也就是说一个产品类就是自身的工厂。这样把原有三个独立的角色:抽象产品角色、具体产品角色和工厂角色合并为一个,这个类自己负责创建自己的实例。

注:以上演变可以从上面的程序代码中直接修改而来,这里我就不贴代码了。

4.10、模式优缺点

工厂类含有必要的判断逻辑,可以决定在什么时候创建哪一个产品类的实例,客户端可以免除直接创建产品对象的责任,而仅仅"消费"产品。简单工厂模式通过这种做法实现了对责任的分割。

当产品有复杂的多层等级结构时,工厂类只有自己,以不变应万变,就是模式的缺点。因为工厂类集中了所有产品创建逻辑,一旦不能正常工作,整个系统都要受到影响。

系统扩展困难,一旦添加新产品就不得不修改工厂逻辑,有可能造成工厂逻辑过于复杂,违背了"开放--封闭"原则(OCP).另外,简单工厂模式通常使用静态工厂方法,这使得无法由子类继承,造成工厂角色无法形成基于继承的等级结构。

4.11、相关模式

工厂方法模式:每个产品由一个专门的工厂来负责创建。是一种只有唯一一个产品的实现,带有简单工厂的性质。

抽象工厂模式:大致和工厂方法相同。

单例模式:单例的实现和上面模式演变中的最后一种很相似,只要把构造器私有便OK。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: