简单工厂模式

Posted <烟花易冷>

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了简单工厂模式相关的知识,希望对你有一定的参考价值。

工厂模式1.

在简单工厂模式中,需要由某个类充当指挥者,决定集成层次中的哪一个子类被实例化。

工厂方法(Factory Method)模式是这一想法的一个聪明而又巧妙的扩展,在工厂方法模式中,不会由单个类来决定实例化哪一个子类,相反,父类把这一决定推迟到了每一个子类中。该模式实际上并不存在一个决策点,某个子类由另一个类直接选中,依照这一模式编写的程序定义了一个抽象类,该类创建对象,但是让每个子类自己来决定创建哪一个对象。

拿游泳比赛泳道排位的算法来做个比较,由于我看到资料中游泳比赛的排位描述不清,所以我们这里不研究游泳比赛具体的排位算法,只是探讨工厂方法模式的应用。

简单的知道游泳比赛排位可以分为直接排位和循环排位。

 

类的层次结构视图。

设计一个赛事类,为Event类,类的定义如下:

<pre class="html" name="code">using System;  
02.using System.Collections;  
03.using CsharpPats;  
04.  
05.namespace Seeding  
06.{  
07.    /// <summary>  
08.    /// Summary description for Event.  
09.    /// </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.}</pre><br>  
34.<pre></pre>  
35.<p> </p>  
36.<p>接着我们可以派生两个赛事类,都继承了Event 类,这两个派生的名字分别为PrelimEvent和 TimedFinalEvent,这两个类的不同之处仅仅在于一个返回某种排位方式而另外一个类返回另外一种排位方式。</p>  
37.<p> </p>  
38.<p>我们定义一个抽象的排位类Seeding:</p>  
39.<pre class="html" name="code">using System;  
40.using System.Collections ;  
41.namespace Seeding  
42.{  
43.    /// <summary>  
44.    /// Summary description for Seeding.  
45.    /// </summary>  
46.    public abstract class Seeding   {  
47.        protected int       numLanes;                        
48.        protected int[]     lanes;  
49.        public abstract IEnumerator getSwimmers();  
50.        public abstract int getCount();  
51.        public abstract int getHeats();  
52.        protected abstract void seed();  
53.        //--------------------------------  
54.        protected void calcLaneOrder() {  
55.            lanes = new int[numLanes];  
56.            int mid = numLanes / 2;  
57.            if (odd(numLanes))  
58.                mid = mid + 1;       //start in middle lane  
59.            int incr = 1;  
60.            int ln = mid;  
61.            //create array of lanes from  
62.            //center to outside  
63.            for (int i=0; i< numLanes; i++) {  
64.                lanes[i] = ln;  
65.                ln = mid + incr;  
66.                incr = - incr;  
67.                if (incr > 0)  
68.                    incr=incr+1;  
69.            }  
70.        }  
71.        //--------------------------------  
72.        private bool odd(int x) {  
73.            return(((x / 2)*2) != x);  
74.        }  
75.    }  
76.}  
77.</pre>  
78.<p><br>  
79.接下来我们可以创建两个具体的排位子类:StraightSeeding和CircleSeeding。PrelimEvent将会返回CircleSeeding类的一个实例,而TimedFinalEvent类则返回StraightSeeding的一个实例。这样以来,我们就有两个层次结构,一个是Event类的,一个是Seeding类的。</p>  
80.<p>在Event类的层次结构中,你会看到两个派生于Event类的类都包含一个getSeeding()方法,其中的一个返回StraightSeeding,而另外一个则返回CircleSeeding。这样,我们可以看出,这里不存在一个实际上的工厂决策点,而是由实例化哪一个Event类的决定来确定哪一个Seeding的子类会呗实例化。</p>  
81.<p>简单的说,就是在Event类的子类中,不同的子类创建不同的Seeding类的子类对象,创建不同的Event类的子类,就对应着创建了不同的Seeding类的子类,而不是像简单工厂模式那样存在一个决策点来决定创建哪一个子类。</p>  
82.<p> </p>  
83.<p>程序中用到了Swimmer类,这个类包含了姓名,所属俱乐部年龄排名时间记录泳道等信息。</p>  
84.<p> </p>  
85.<p>在Event类的基础上,我们派生了PrelimEvent和 TimedFinalEvent两个类,Event基类中包含了判断赛事是预赛还是决赛或者计时决赛的空方法。</p>  
86.<p>PrelimEvent的定义:</p>  
87.<pre class="html" name="code">using System;  
88.  
89.namespace Seeding  
90.{  
91.    /// <summary>  
92.    /// Summary description for PrelimEvent.  
93.    /// </summary>  
94.    public class PrelimEvent:Event  
95.    {  
96.        public PrelimEvent(string filename, int lanes):base(filename,lanes) {  
97.                    }  
98.        //return circle seeding  
99.        public override Seeding getSeeding() {  
100.            return new CircleSeeding(swimmers, numLanes);  
101.        }  
102.        public override bool isPrelim() {  
103.            return true;  
104.        }  
105.        public override bool isFinal() {  
106.            return false;  
107.        }  
108.        public override bool isTimedFinal() {  
109.            return false;  
110.        }  
111.    }  
112.}</pre><pre class="html" name="code"> </pre><pre class="html" name="code">请注意,PrelimEvent类中,getSeeding()方法返回的是CircleSeeding类。</pre><pre class="html" name="code">TimedFinalEvent的定义:</pre><pre class="html" name="code"><pre class="html" name="code">using System;  
113.  
114.namespace Seeding {  
115.    /// <summary>  
116.    ///class describes an event that will be swum twice  
117.    /// </summary>  
118.    public class TimedFinalEvent:Event  {  
119.      
120.        public TimedFinalEvent(string filename, int lanes):base(filename, lanes) {  
121.        }  
122.            //return StraightSeeding class  
123.            public override Seeding getSeeding() {  
124.                return new StraightSeeding(swimmers, numLanes);  
125.            }  
126.        public override bool isPrelim() {  
127.            return false;  
128.        }  
129.        public override bool isFinal() {  
130.            return false;  
131.        }  
132.        public override bool isTimedFinal() {  
133.            return true;  
134.        }  
135.    }  
136.}  
137.</pre><br>  
138.<pre></pre>  
139.<pre class="html" name="code">在这个类中,,getSeeding()方法返回的是StraightSeeding类。</pre><pre class="html" name="code"> </pre><pre class="html" name="code">根据返回的排序类的不同,就可以在不同的赛事中做出不同的排序。</pre><pre class="html" name="code"> </pre><pre class="html" name="code">StraightSeeding和CircleSeeding两个子类的实现这里不给出了,我们不用关心具体的算法问题。</pre><pre class="html" name="code">类的层次结构视图如图:见上。</pre><pre class="html" name="code"> </pre><pre class="html" name="code"> </pre><pre class="html" name="code">在应用程序初始化的时候,调用了如下的代码:</pre><pre class="html" name="code"><pre class="html" name="code">private void init() {  
140.            //create array of events  
141.            events = new ArrayList ();  
142.            lsEvents.Items.Add ("500 Free");  
143.            lsEvents.Items.Add ("100 Free");  
144.            //and read in their data  
145.            events.Add (new TimedFinalEvent ("500free.txt", 6));  
146.            events.Add (new PrelimEvent ("100free.txt", 6));  
147.        }</pre><br>  
148.<pre></pre>  
149.<pre class="html" name="code">这个过程创建了两个Event类的子类的实例,每个子类一个实例,所以在工厂方法模式中,不存在一个决策点来决定创建哪个子类,而是将这一决定推迟到每一个子类中,例如我们这里的例子中,每一个Event子类对应一个排序类的引用,先是生成多个Event类子类的实例,但是到最后应用的时候只有一个实例被选中。</pre><pre class="html" name="code">事件的调用:</pre><pre class="html" name="code"><pre class="html" name="code">private void lsEvents_SelectedIndexChanged(object sender, System.EventArgs e) {  
150.            int index = lsEvents.SelectedIndex ;  
151.            Event ev = (Event)events[index];  
152.            Seeding sd = ev.getSeeding();  
153.            IEnumerator en  = sd.getSwimmers();  
154.            lsSwimmers.Items.Clear() ;  
155.            while(en.MoveNext ()) {  
156.                Swimmer sw = (Swimmer)en.Current ;  
157.                lsSwimmers.Items.Add(sw.getHeat()+" "+sw.getLane()+" "+sw.getName()+" "+sw.getTime());  
158.            }  
159.        }</pre><br>  
160.<pre></pre>  
161.<pre class="html" name="code">这段代码中,通过获取列表框中的选取值来判断Event类的哪一个子类应该呗选中,然后通过这个选中子类调用这个子类对应的Seeding类的哪个子类,从而进行排序。</pre><pre class="html" name="code">使用工厂方法模式还是利用了面向对象的多态性,利用一个Event类的数组来保存对所有派生类实例的引用,然后确定应该选中哪一个子类,由于多态性,利用Event类的方法即可调用这个派生类的派生方法。</pre><pre class="html" name="code">使用工厂方法模式应该注意的问题:</pre><pre class="html" name="code">1. 类不能预测他必须创建哪一种类型的对象。</pre><pre class="html" name="code">2. 类使用他的子类来指定要创建的对象。</pre><pre class="html" name="code">3. 希望只有局部知晓哪个类会被创建。</pre>  
162.<pre></pre>  
163.<pre></pre>  
164.<pre></pre>  
165.<pre></pre>  
166.<pre></pre>  
167.<pre></pre>  
168.<pre></pre>  
169.<pre></pre>  
170.     
171.</pre></pre></pre>

 工厂模式2.

简单工厂模式根据提供的数据或者参数返回几个可能的类中的一个实例,说通俗点有点像面向对象编程中的多态性,一个基类,有多个派生类,在另外的调用程序中,根据参数来决定返回这个基类的哪个具体的派生类,返回值为基类类型,因为基类的引用可以指向派生类对象,而且这些所有的派生类都包含有基类的函数,也就是说派生类中有相同的函数,但是函数的实现可能不同。

在简单工厂模式中,通常其返回的所有的类都有一个共同的基类和一些共同的方法,不过每一个类都完成不同的功能,并针对不同的数据类型做了不同的优化。简单工厂模式实际上并不是23个常用设计模式之一,但是也算是设计模式的简单入门,对以后的学习有比较大的帮助。

 

简单的理解,简单工厂模式更多的使用了面向对象编程中的多态性,基类的引用可以指向派生类,并且调用派生的方法,即多态性。

 

假定X是一个基类,XY和XZ都是派生于X的派生类,XFactory类中根据提供给它的参数来决定返回XY或者 类的哪一个,返回的是哪个类的实例对于编程者来说并不是那么重要,因为这些类有着相同的方法,编程者需要做的就是通过基类引用直接调用方法,不用去关心到底返回的是哪个派生类,因为这些类有相同的方法,只是实现不同而已。如何决定返回哪一个派生类,取决于工厂的设计,该方法可以是非常复杂的函数,也可以是简单的函数。

 假设我们有一个类,用来存放名字,其中包含名字的姓和名分别存放。这个类的基本代码如下:

using System;  
02.  
03.namespace NameFactory  
04.{  
05.    /// <summary>  
06.    /// Summary description for Namer.  
07.    /// </summary>  
08.    //Base class for getting split names  
09.    public class Namer  {  
10.        //parts stored here  
11.        protected string frName, lName;  
12.          
13.        //return first name  
14.        public string getFrname(){  
15.            return frName;  
16.        }  
17.        //return last name  
18.        public string getLname() {  
19.            return lName;  
20.        }  
21.    }  
22.}

在这个类中可以分别返回名和姓。protected string frName, lName;两个受保护的变量可以被派生类继承。

 

现在我们派生两个非常简单的派生类,并在派生类中把姓名分割成两个部分。如果是姓在前,名在后,中间用空格分开,责是FirstFirst类,如果是姓在后,名在前,中间用“,”分开,则是LastFirst类。两个类的设计分别为:

using System;  
02.  
03.namespace NameFactory  
04.{  
05.    /// <summary>  
06.    /// Summary description for FirstFirst.  
07.    /// </summary>  
08.    public class FirstFirst : Namer  
09.    {  
10.        public FirstFirst(string name)  
11.        {  
12.            int i = name.IndexOf (" ");  
13.            if(i > 0) {  
14.                frName = name.Substring (0, i).Trim ();  
15.                lName = name.Substring (i + 1).Trim ();  
16.            }  
17.            else {  
18.                lName = name;  
19.                frName = "";  
20.            }  
21.        }  
22.    }  
23.}
using System;  
02.  
03.namespace NameFactory  
04.{  
05.    /// <summary>  
06.    /// Summary description for LastFirst.  
07.    /// </summary>  
08.    public class LastFirst : Namer  
09.    {  
10.        public LastFirst(string name)       {  
11.            int i = name.IndexOf (",");  
12.            if(i > 0) {  
13.                lName = name.Substring (0, i);  
14.                frName = name.Substring (i + 1).Trim ();  
15.            }  
16.            else {  
17.                lName = name;  
18.                frName = "";  
19.            }  
20.        }  
21.    }  
22.}

两个派生类中构造函数根据是不是有“,”来将名字中的姓和名分割开来,分别保存的对应的变量中。

然后我们就可以构建简单的工厂了,只需要检测逗号的存在,然后返回两个类中的一个就可以了。

using System;  
02.  
03.namespace NameFactory  
04.{  
05.    /// <summary>  
06.    /// Summary description for NameFactory.  
07.    /// </summary>  
08.    public class NameFactory    {  
09.        public NameFactory() {}  
10.  
11.        public Namer getName(string name) {  
12.            int i = name.IndexOf (",");  
13.            if(i > 0)  
14.                return new LastFirst (name);  
15.            else  
16.                return new FirstFirst (name);  
17.        }  
18.    }  
19.}

这个工厂类的使用,添加一个按钮,按钮事件中调用NameFactory的getName方法,就可以返回一个Namer的引用,至于Namer到底指向哪个派生类,我们不用去关心。

private void btCompute_Click(object sender, System.EventArgs e) {  
02.            Namer nm = nameFact.getName (txName.Text );  
03.            txFirst.Text = nm.getFrname ();  
04.            txLast.Text = nm.getLname ();  
05.        } 

这就是简单工厂模式的基本原理,创建一个抽象,然后该抽象决定要返回的可能的几个类是哪些,接着简单工厂返回其中之一,然后就可以在无需知道真正使用的哪个子类的情况下调用返回的类实例的方法,这一做法把数据依赖问题与类的使用方法隔离开来。

简单工厂返回有着相同方法的类实例,他们有可能是不同的派生类的实例,或者有可能实际上是互不相关的类,只是共有相同的接口而已。无论是哪种方式,这些类实例的方法都是相同的,可以互换使用。

 

以上是关于简单工厂模式的主要内容,如果未能解决你的问题,请参考以下文章

设计模式学习——简单工厂模式工厂模式抽象工厂模式

PHP面向对象之选择工厂和更新工厂

设计模式-简单工厂工厂方法模式抽象工厂模式详解

设计模式之简单工厂模式

C++工厂模式(简单工厂工厂方法抽象工厂)

C++工厂模式(简单工厂工厂方法抽象工厂)