设计模式 工厂方法(Factory Method Pattern)

工厂方法

工厂的概念反复出现在面向对象程序设计中,在C#本身和其他设计模式(例如生成器模式)中,就能找到几个例子。在这个例子中,有一个类负责决定在单继承体系结构中实例化哪一个字类。

  工厂方法模式(Factory Method Pattern)对这种思想进行了巧妙的扩展,它不是用一个专门的类来决定实例化那一个字类,相反,超类把这种决定延迟
到没个子类。这种设计模式实际上没有决策点,即没有直接选择一个字类实例化的决策。按照这种模式编写的程序定义了一个抽象类,他去创建对象,
但让子类决定创建哪一种对象。


  这里考虑一个相当简单的例子,在游泳比赛中为运动员确定泳道。在一个赛事中,游泳选手完成几次预赛后,按照前面预赛中最慢的到最后预赛中最快的顺序,
对运动员的成绩进行排序,在接下来的比赛中,把游的最快的选手安排在中央泳道上。这种确定泳道的方式成为直接排位。
  目前,游泳选手参加锦标赛时,通常游两次,每个选手都参加预赛,前12名或16名会在决赛在比一次,为了使预赛更公平,对预赛循环排位:
最快的三名选手安排在最快的中央道上,第二快的三名选手排在头三组的邻中央泳道上,依此类推。

  我们怎样构建对象才能实现这种用到分配机制并说明工厂模式呢?首先设计一耳光抽象类Event

 1 using System;
2 using System.Collections;
3 using CsharpPats;
4
5 namespace Seeding
6 {
7 /// <summary>
8 /// Summary description for Event.
9 /// </summary>
10 public abstract class Event {
11 protected int numLanes;
12 protected ArrayList swimmers;
13
14 public Event(string filename, int lanes) {
15 numLanes = lanes;
16 swimmers = new ArrayList();
17 //read in swimmers from file
18 csFile f = new csFile(filename);
19 f.OpenForRead ();
20 string s = f.readLine();
21 while (s != null) {
22 Swimmer sw = new Swimmer(s);
23 swimmers.Add (sw);
24 s = f.readLine();
25 }
26 f.close();
27 }
28 public abstract Seeding getSeeding();
29 public abstract bool isPrelim();
30 public abstract bool isFinal();
31 public abstract bool isTimedFinal();
32 }
33 }

这些抽象方法表明了具体Event类应该实现的部分。接下来就有Event类派生两个具体的类,
分别为PrelimEvent类和TimedFinalEvent类。这两个类唯一的差别就是,一个类返回的是泳道分配方法方式,另一个是返回另一种泳道分配方式。我们还定义带有如下方法的抽象类Seeding

 1 using System;
2 using System.Collections ;
3 namespace Seeding
4 {
5 /// <summary>
6 /// Summary description for Seeding.
7 /// </summary>
8 public abstract class Seeding {
9 protected int numLanes;
10 protected int[] lanes;
11 public abstract IEnumerator getSwimmers();
12 public abstract int getCount();
13 public abstract int getHeats();
14 protected abstract void seed();
15 //--------------------------------
16 protected void calcLaneOrder() {
17 lanes = new int[numLanes];
18 int mid = numLanes / 2;
19 if (odd(numLanes))
20 mid = mid + 1; //start in middle lane
21 int incr = 1;
22 int ln = mid;
23 //create array of lanes from
24 //center to outside
25 for (int i=0; i< numLanes; i++) {
26 lanes[i] = ln;
27 ln = mid + incr;
28 incr = - incr;
29 if (incr > 0)
30 incr=incr+1;
31 }
32 }
33 //--------------------------------
34 private bool odd(int x) {
35 return(((x / 2)*2) != x);
36 }
37 }
38 }

接下来创建两个具体的Seeding子类:StraightSeeding 类和CircleSeeding类,PrelimEvent类会返回一个CircleSeeding的实例,而TimedFinalEvent类返回一个StraightSeeding的实例。
这样我们就有两个体系结构:一个是关于Event 的一个似乎关于Seeding 的。
在Event继承结构中(如下图)会看到两个Event的派生类,他们含有getSeeding方法一个返回StraightSeeding实例,另一个返回CircleSeeding的实例。
可以看出,它与我们前面的例子不同没有实际的工厂决策点。实例化哪一个Event类的决策就是决定实例化哪一个Seeding类 的决策。
在两个类继承体系结构中,尽管看起来是一对一的对应关系,但不是必须的。可以有许多这种Event类,而只有使用几种Seeding类。

 

Swimmer类
除了说过的Swimmer类含有名字、俱乐部、年龄、排位、时间以及选拔赛后的分组和泳道外,Swimmer类我们并没有介绍太多。Event类从某个数据库
读入Swimmer数据,然后再某项赛事中调用getSeeding方法时,将数据传给Seeding类。

 1 using System;
2 using CsharpPats;
3
4 namespace Seeding
5 {
6 /// <summary>
7 /// Summary description for Swimmer.
8 /// </summary>
9 public class Swimmer
10 {
11 private string firstName, lastName;
12 private int age;
13 private string club;
14 private float time;
15
16 private int heat, lane;
17 //--------------------------------------
18 public Swimmer(String dataline) {
19 StringTokenizer st = new StringTokenizer(dataline, " ");
20 string lineNumber = st.nextToken(); //ignore and discard
21 firstName = st.nextToken();
22 lastName = st.nextToken();
23 age = Convert.ToInt32 (st.nextToken().Trim());
24 club = st.nextToken().Trim();
25
26 string stime = st.nextToken().Trim();
27 int i = stime.IndexOf(":");
28 if (i > 0) {
29 stime = stime.Substring(0, i) + stime.Substring (i+1);
30 }
31 time = Convert.ToSingle ( stime);
32
33 }
34
35 //-------------------------------
36 public void setLane(int ln) {
37 lane = ln;
38 }
39 //-------------------------------
40 public int getLane() {
41 return lane;
42 }
43 //-------------------------------
44 public void setHeat(int ht) {
45 heat = ht;
46 }
47 //-------------------------------
48 public int getHeat() {
49 return heat;
50 }
51 //-------------------------------
52 public int getAge() {
53 return age;
54 }
55 //-------------------------------
56 public float getTime() {
57 return time;
58 }
59 //-------------------------------
60 public string getName() {
61 return firstName+" "+lastName;
62 }
63 //-------------------------------
64 public string getClub() {
65 return club;
66 }
67
68 }
69 }

Event类
我们在前面已经看到了抽象基类Event。在实际中,用它读入选手数据,并将其传给Swimmer类的一个实例去进行分析。在抽象基类Event里,判断赛事是预赛、
决赛还是计时决赛的方法都是空的、在派生类中会实现这些方法。PrelimEvent返回CircleSeeding的一个实例

 1 using System;
2
3 namespace Seeding
4 {
5 /// <summary>
6 /// Summary description for PrelimEvent.
7 /// </summary>
8 public class PrelimEvent:Event
9 {
10 public PrelimEvent(string filename, int lanes):base(filename,lanes) {
11 }
12 //return circle seeding
13 public override Seeding getSeeding() {
14 return new CircleSeeding(swimmers, numLanes);
15 }
16 public override bool isPrelim() {
17 return true;
18 }
19 public override bool isFinal() {
20 return false;
21 }
22 public override bool isTimedFinal() {
23 return false;
24 }
25 }
26 }

 

TimedFinalEvent返回StraightSeeding的一个实例

 1 using System;
2
3 namespace Seeding {
4 /// <summary>
5 ///class describes an event that will be swum twice
6 /// </summary>
7 public class TimedFinalEvent:Event {
8
9 public TimedFinalEvent(string filename, int lanes):base(filename, lanes) {
10 }
11 //return StraightSeeding class
12 public override Seeding getSeeding() {
13 return new StraightSeeding(swimmers, numLanes);
14 }
15 public override bool isPrelim() {
16 return false;
17 }
18 public override bool isFinal() {
19 return false;
20 }
21 public override bool isTimedFinal() {
22 return true;
23 }
24 }
25 }

直接排位
实际编写这个程序时,我们发现大多数工作都是在直接排位(StraightSeeding)中完成的,为了循环排位(CircleSeeding)而进行的改动相当少。
实例化StraightSeeding类,并拷如选手和泳道号。

 1         protected override void seed() {
2 //loads the swmrs array and sorts it
3 sortUpwards();
4
5 int lastHeat = count % numLanes;
6 if (lastHeat < 3)
7 lastHeat = 3; //last heat must have 3 or more
8 int lastLanes = count - lastHeat;
9 numHeats = count / numLanes;
10 if (lastLanes > 0)
11 numHeats++;
12 int heats = numHeats;
13
14 //place heat and lane in each swimmer's object
15 int j = 0;
16 for (int i = 0; i < lastLanes; i++) {
17 Swimmer sw = swmrs[i];
18 sw.setLane(lanes[j++]);
19 sw.setHeat(heats);
20 if (j >= numLanes) {
21 heats--;
22 j=0;
23 }
24 }
25 //Add in last partial heat
26 if (j < numLanes)
27 heats--;
28 j = 0;
29 for (int i = lastLanes-1; i<count; i++) {
30 Swimmer sw = swmrs[i];
31 sw.setLane(lanes[j++]);
32 sw.setHeat(heats);
33 }
34 //copy from array back into ArrayList
35 swimmers = new ArrayList();
36 for (int i=0; i< count; i++)
37 swimmers.Add(swmrs[i]);
38 }



循环排位
CircleSeeding 类从StraightSeeding类派生类中,因此一开始先调用父类的seed方法,然后重新安排赛事

 1     protected override void seed() {
2 int circle;
3
4 base.seed(); //do straight seed as default
5 if (numHeats >= 2 ) {
6 if (numHeats >= 3)
7 circle = 3;
8 else
9 circle = 2;
10 int i = 0;
11 for (int j = 0; j < numLanes; j++) {
12 for (int k = 0; k < circle; k++) {
13 swmrs[i].setLane(lanes[j]);
14 swmrs[i++].setHeat(numHeats - k);
15 }
16 }
17 }
18 }

其他工厂

我们在前面略过了一个问题:读入运动员数据程序如何决定生成那一项赛事。再读入数据时,通过正确类型的事件来巧妙的实现这一点。

1         private void init() {
2 //create array of events
3 events = new ArrayList ();
4 lsEvents.Items.Add ("500 Free");
5 lsEvents.Items.Add ("100 Free");
6 //and read in their data
7 events.Add (new TimedFinalEvent ("500free.txt", 6));
8 events.Add (new PrelimEvent ("100free.txt", 6));
9 }

很明显、这是一个需要EventFactory来决定生成哪一项赛事的例子,这里有涉及到前面的简单工厂了!...

什么场景下能使用工厂模式如一下情况

1、一类无法预测他要创建的对象属于那一个类。

2、一类用它的子类来指定所创建的对象。

3、把要创建哪一类的信息局部化的时候。

posted @ 2012-03-27 11:45  孙金棚  阅读(1150)  评论(4编辑  收藏  举报