面向对象的设计模式系列之二:工厂方法模式(FactoryMethod)
在上一篇文章中(Singleton设计模式)曾谈到了工厂模式,那究竟什么是工厂模式呢?它又能解决什么样的问题呢?在知晓这个问题之前,让我们先了解一个概念:对象耦合。经常在很多技术书籍上听到"松耦合,高内聚"。那什么是松耦合呢?既然有松耦合,那应该存在相对的紧耦合吧。两者之间到底有什么联系呢?
带着这些问题,我们可以展开为什么需要工厂模式的缘由。松耦合即在软件系统中,模块与模块之间在应对软件变化(主要来自需求变化)时,一些模块很容易被替换或更改,而能使其他模块保持不变。相对的,紧耦合即软件应对变化时,一些软件的改变将会使相关模块发生改变。先看看下面的两幅图:
图1——紧耦合 图2——松耦合
我们在这里姑且说明一下,两图中的每根线代表系统的模块,图2中的圆圈代表接口。
从图1中我们可以看出,各个系统模块之间相互依赖,当我们面对需求改变时,也就是说图1中有几根线需要改变时,与之相关的线将因此随之而改变,导致整个系统处于不稳定状态之中。而图2中,我们假设中间的那根长线为系统的主模块,而在主模块上附加的那些分支为次模块,并且此模块与主模块之间是以接口来建立依赖关系,各此模块之间彼此相对独立,这样当其中几个次模块改变时,不会影响其他的次模块。从两图中我们不难发现,图2较图1更优越,能够保持整个软件系统的稳定性和可维护性。由此我们总结:当我们在设计软件系统时,首先必须分清系统模块的主次,并建立起主模块和多个次模块,主模块与次模块之间以接口的方式相互依赖,各次模块之间通过主模块相互贯通。也即是:高层模块(主模块)不直接依赖于低层模块(次模块),低层模块的变动不会影响到高层模块和其他相关次模块的变动。相信到这里,大家对这个概念应该有充分的认识了,接下来我们开始工厂模式的讲解了,首先以简单工厂开始吧。
设计意图:定义一个用于创建对象的接口(或抽象类),让子类决定实例化哪一个类,客户端却不知道具体的实现类。
应用场景: 在软件系统中,经常面临着"某个对象"的创建工作,但由于需求的变化导致这个对象的创建方式剧烈的变化,但是它却拥有相对稳定的接口。如何应对这种变化呢?需要提供一种"封装机制"来隔离这个"易变对象"的变化,而使"依赖于该易变对象的其他对象"不随着需求改变而改变。
实例案例:现在存在一套Web系统是在SQL Server数据库环境下工作,而且也运行良好。但突然有一天由于公司业务量的增长,数据量大为剧增可能需要往Oracle迁移,实现数据库的完全切换,可能原有的支持SQL Server数据库环境的数据访问底层已经无法满足现在业务的需要,但又不能去直接改变底层访问的代码,可能以后还有其他数据库来支撑。我们就会想到使用统一的数据库访问接口,让SQL Server,Oracle以及其他数据库都支持这套接口。这样做固然解决支持多数据库问题,但问题在于客户端调用时,怎样去实例化该接口对应的数据库类型,在这种情况下简单工厂就能很好解决这个创建获取数据库类型的问题了。
/// <summary>
/// 定义数据库提供者基类
/// </summary>
public abstract class DBProvider
{
protected string connectionString = String.Empty;
protected DbConnection Connection = null;
public DBProvider(string connectionString)
{
this.connectionString = connectionString;
}
public abstract void Connect();
public abstract bool CheckConect();
public abstract DataTable ExecuteDataSet(string selectSql);
public abstract bool ExecuteInsert(string insertSql);
public abstract bool ExecuteUpdate(string updateSql);
public abstract bool ExecuteDelete(string deleteSql);
}
接下来是Sql Server提供者实现类:
/// <summary>
/// Sql Server提供者实现
/// </summary>
public class SqlServerProvider : DBProvider
{
public SqlServerProvider(string connectionString)
: base(connectionString)
{
Connection = new SqlConnection(connectionString);
}
public override void Connect()
{
//SqlServer Connect
}
public override bool CheckConect()
{
return true;
}
public override DataTable ExecuteDataSet(string selectSql)
{
return null;
}
public override bool ExecuteInsert(string insertSql)
{
return true;
}
public override bool ExecuteUpdate(string updateSql)
{
return true;
}
public override bool ExecuteDelete(string deleteSql)
{
return true;
}
}
接下来是Oracle提供者实现类:
/// <summary>
/// Oracle提供者实现
/// </summary>
public class OracleProvider : DBProvider
{
public OracleProvider(string connectionString)
: base(connectionString)
{
Connection = new OracleConnection(connectionString);
}
public override void Connect()
{
//Oracle Connect
}
public override bool CheckConect()
{
return true;
}
public override DataTable ExecuteDataSet(string selectSql)
{
return null;
}
public override bool ExecuteInsert(string insertSql)
{
return true;
}
public override bool ExecuteUpdate(string updateSql)
{
return true;
}
public override bool ExecuteDelete(string deleteSql)
{
return true;
}
}
最后是获取提供者工厂类:
/// <summary>
/// 数据库类型枚举
/// </summary>
public enum DataBaseType
{
SqlServer,
Oracle
}
/// <summary>
/// 获取数据库提供者工厂
/// </summary>
public class DBFactory
{
public static DBProvider CreateDBProvider(DataBaseType dbType, string connectionString)
{
switch (dbType)
{
case DataBaseType.SqlServer:
default:
return new SqlServerProvider(connectionString);
case DataBaseType.Oracle:
return new OracleProvider(connectionString);
}
}
}
在客户端访问:
public class Program
{
public static void Main()
{
var sqlserverProvider = DBFactory.CreateDBProvider(DataBaseType.SqlServer, "server=***;database=***;uid=***;password=***");
var oracleProvider = DBFactory.CreateDBProvider(DataBaseType.Oracle, "Data Source=(DESCRIPTION=(ADDRESS_LIST=(ADDRESS=(PROTOCOL=TCP)(HOST=MyHost)(PORT=MyPort)))(CONNECT_DATA=(SERVER=DEDICATED)(SERVICE_NAME=***)));User Id=***;Password=***;");
}
}
此时发现如何获取数据库提供者的职责完全交给工厂去做了,客户端只需要传入对应的数据库类型和连接字符串就可以获取到对应的数据库提供者,是不是非常简单呢?在这里,我们不妨总结一下:按松耦合思路,客户端只依赖于数据库提供者工厂,而不依赖于具体的数据库提供者,在一定程度上实现了从SQL Server向Oracle迁移的过渡。对于示例而言,主模块是客户端获取数据库提供者接口CreateDBProvider(),只要保持该接口不发生变化,即使其中某个具体的数据库提供者发生变化也不会影响到其他的数据库提供者,这正是松耦合的主旨所在。从这个例子中也能总结出简单工厂的适用范围:要实例化的子类的数量有限且无复杂层级关系,并且可以在编译之前就可以提前预知,例如前面的数据库类型枚举。如果要增加新的类型,势必导致子类的类型增加和工厂的变动(增加枚举标识和switch判断),这无疑与我们的"松耦合"设计理念相违背(由于次模块的改变导致主模块随之变动),因此我们提出了工厂方法模式来解决这个问题。
首先,我们要明确工厂模式所面临的问题,简单工厂通过传递参数的方式来决定实例化哪个子类,这种方式的弊端在于子类的增加可能是动态的,在编译之前不可预知到。而我们的工厂创建方法属于主模块,不应该随着子类的增加而发生变动。在前面意图中已经说了,"定义一个用于创建对象的接口(或抽象类),让子类决定实例化哪一个类",既然要增加子类,而工厂又不想发生改变,那我们想是否可以讲工厂的创建操作提取出来,成为一个工厂基类呢?将具体创建的工作放到工厂的子类中去完成对应的工作呢?这正是工厂方法模式的由来。所以常有人说:简单工厂其实只是"参数化的工厂方法模式",只是工厂方法的一个特例,因此GOF也没有将其列入23种设计模式中来。但即便如此,简单工厂还是在我们的项目应用中起到非常关键的作用,只是有它的适用范围罢了。
我举个很形象的例子来说明一下工厂模式。大家都知道鸟生蛋这个简单的例子,鸟和蛋是两个虚拟的概念,只有实例化到具体的鸟才能生具体的蛋,脱离了具体的鸟的对象,蛋也就无从谈起了。那假设目前有鸡、鸭、鹅三种鸟,那对应的就有鸡蛋、鸭蛋、鹅蛋,而下蛋的动作是每种鸟必备的,我们只需要创建生成鸟的工厂方法即可。如果采用以前的简单工厂将无法满足要求,随着后面鸟类的增加,就必须改动创建鸟这个工厂方法,因此只能为每一种鸟产生一个具体的创建工厂。
接下来我一个具体的示例来说明工厂方法模式的应用。为了能给大家一个更好展示模式应用过程,我采用迭代式编程的方式向大家逐步介绍。假设目前有一个视频播放器工厂,需要生产一批既可以支持RealPlayer,又可以支持MS Media Player,可能以后还有其他形式的。首先我们定义视频播放器基类。
/// <summary>
/// 视频播放器基类(可启动,播放,暂停以及停止)
/// </summary>
public abstract class VideoPlayer
{
public abstract void StartUp();
public abstract void Play();
public abstract void Pause(int inteval);
public abstract void Stop();
}
/// <summary>
/// 支持RealPlayer的视频播放器
/// </summary>
public class RealPlayer : VideoPlayer
{
public override void StartUp()
{
Console.WriteLine("RealPlayer is starting...");
}
public override void Play()
{
Console.WriteLine("RealPlayer is playing...");
}
public override void Pause(int inteval)
{
Console.WriteLine("RealPlayer is paused by " + inteval + "minutes.");
}
public override void Stop()
{
Console.WriteLine("RealPlayer has stopped.");
}
}
/// <summary>
/// 创建RealPlayer的视频播放器工厂
/// </summary>
public class RealPlayerFactory
{
public static RealPlayer CreateRealPlayer()
{
return new RealPlayer();
}
}
[TestMethod]
public void TestRealPlayerFactory()
{
var realPlayer = RealPlayerFactory.CreateRealPlayer();
Assert.IsNotNull(realPlayer);
realPlayer.StartUp();//RealPlayer is starting...
realPlayer.Play();//RealPlayer is playing...
realPlayer.Pause(2);//RealPlayer is paused by 2 minutes.
realPlayer.Stop();//RealPlayer has stopped.
}
从单元测试类中,我们可以看出客户端依赖于RealPlayerFactory工厂,如果有一天用户需要支持MS Media Player播放器,势必要生成一个MSMediaPlayerFactory用于创建支持MS Media Player的播放器。然后客户端只需要将RealPlayerFactory换成MSMediaPlayerFactory,视频播放器就会支持MS Media Player了。这就是与简单工厂无止境的实例类型注入和switch类型判断相区别。
/// <summary>
/// 支持MS Media Player的视频播放器
/// </summary>
public class MSMediaPLayer : VideoPlayer
{
public override void StartUp()
{
Console.WriteLine("MSMediaPLayer is starting...");
}
public override void Play()
{
Console.WriteLine("MSMediaPLayer is playing...");
}
public override void Pause(int inteval)
{
Console.WriteLine("MSMediaPLayer is paused by " + inteval + "minutes.");
}
public override void Stop()
{
Console.WriteLine("MSMediaPLayer has stopped.");
}
}
/// <summary>
/// 创建MSMediaPLayer的视频播放器工厂
/// </summary>
public class MSMediaPLayerFactory
{
public static MSMediaPLayer CreateMSMediaPLayer()
{
return new MSMediaPLayer();
}
}
[TestMethod]
public void TestMSMediaPlayerFactory()
{
var msmediaPlayer = MSMediaPLayerFactory.CreateMSMediaPLayer();
Assert.IsNotNull(msmediaPlayer);
msmediaPlayer.StartUp();//MSMediaPLayer is starting...
msmediaPlayer.Play();//MSMediaPLayer is playing...
msmediaPlayer.Pause(3);//MSMediaPLayer is paused by 3 minutes.
msmediaPlayer.Stop();//MSMediaPLayer has stopped.
}
细心的朋友可能会发现,我增加支持MS Media Player,只需要添加一种播放器和创建工厂,然后在客户端将创建工厂修改为新工厂即可。值得关注的是,我并未对原有的支持RealPlayer的播放器以及创建工厂做任何更改。接下来,用户可能觉得要同时支持RealPlayer和MSMediaPlayer的播放器,就必须确切知道有对应的RealPlayerFactory和MSMediaPlayerFactory,如果以后有其他的播放器需要支持,那岂不是让用户为难了吗?从前面的例子,很快让我们萌发了一种了提炼工厂的想法。我们现在将创建播放器这个动作提炼到一个抽象类中(其实也可为接口)。经常会在工厂方法内部定义其他的方法,例如模板模式中就会定义很多模板方法。
/// <summary>
/// 视频播放器创建工厂
/// </summary>
public abstract class VideoPlayerFactory
{
public abstract VideoPlayer CreateVideoPlayer();
}
/// <summary>
/// 创建RealPlayer的视频播放器工厂
/// </summary>
public class RealPlayerFactory : VideoPlayerFactory
{
public override VideoPlayer CreateVideoPlayer()
{
return new RealPlayer();
}
}
/// <summary>
/// 创建MSMediaPLayer的视频播放器工厂
/// </summary>
public class MSMediaPLayerFactory : VideoPlayerFactory
{
public override VideoPlayer CreateVideoPlayer()
{
return new MSMediaPLayer();
}
}
建立单元测试类:
[TestMethod]
public void TestVedioPlayerFactory()
{
var realPlayer = new RealPlayerFactory().CreateVideoPlayer();
Assert.IsNotNull(realPlayer);
realPlayer.StartUp();//RealPlayer is starting...
realPlayer.Play();//RealPlayer is playing...
realPlayer.Pause(2);//RealPlayer is paused by 2 minutes.
realPlayer.Stop();//RealPlayer has stopped.
var msmediaPlayer = new MSMediaPLayerFactory().CreateVideoPlayer();
Assert.IsNotNull(msmediaPlayer);
msmediaPlayer.StartUp();//MSMediaPLayer is starting...
msmediaPlayer.Play();//MSMediaPLayer is playing...
msmediaPlayer.Pause(3);//MSMediaPLayer is paused by 3 minutes.
msmediaPlayer.Stop();//MSMediaPLayer has stopped.
}
现在两种播放器都支持了,特别注意抽象工厂类VideoPlayerFactory,以及对应的创建方法public abstract VideoPlayer CreateVideoPlayer();发现返回值不再是具体的哪种播放器而是VideoPlayer抽象播放器,并且各自有各自的创建工厂,实现了一定程度上的松耦合,工厂模式将创建实例的工作推迟到子类工厂中完成,从而实现"扩展而不更改",这也符合"对更改封闭,对扩展开放"的原则(OCP原则),可能有的朋友会说,我的客户端依然依赖于具体的创建工厂。怎么办呢?要是有一种机制可以拿我们刚才创建的VideoPlayerFactory类来完成这个工作该多好,但是大家都知道在这里new VideoPlayerFactory()是不现实的,那我们何不采用配置文件的方式来实现呢?我们在配置文件中加入当前需要支持的播放器标识,结合反射机制来动态读取当前播放器标识,不就解决了这个棘手的问题了。
<configuration>
<appSettings>
<add key="RealPlayerFormat" value="RealPlayer"/>
<add key="MSMediaPlayerFormat" value="MSMediaPlayer"/>
</appSettings>
</configuration>
再次建立单元测试类:
[TestMethod]
public void TestRealPlayerFactoryByConfig()
{
var factory = GetVedioPlayerFactoryByConfig("RealPlayerFormat");
Assert.IsNotNull(factory);
var player = factory.CreateVideoPlayer();
Assert.IsNotNull(player);
Assert.IsTrue(player.GetType() == typeof(RealPlayer));
}
[TestMethod]
public void TestMSMediaPlayerFactoryByConfig()
{
var factory = GetVedioPlayerFactoryByConfig("MSMediaPlayerFormat");
Assert.IsNotNull(factory);
var player = factory.CreateVideoPlayer();
Assert.IsNotNull(player);
Assert.IsTrue(player.GetType() == typeof(MSMediaPlayer));
}
private VideoPlayerFactory GetVedioPlayerFactoryByConfig(string formatKey)
{
var format = ConfigurationManager.AppSettings[formatKey];
var appPath = AppDomain.CurrentDomain.BaseDirectory;
var assembly = Assembly.LoadFrom(Path.Combine(appPath, "DesignPattern.FactoryMethodPattern.dll"));
//创建RealPlayerFactory
var type = "DesignPattern.FactoryMethodPattern.FactoryMethod." + format + "Factory";
var factory = (VideoPlayerFactory)assembly.CreateInstance(type);
return factory;
}
现在来看客户端已经不再依赖于任何具体的创建工厂了,而只能抽象创建工厂类VideoPlayerFactory有关,完全实现了解耦。如需要增加新的播放器,只需要添加新的播放器以及创建工厂,然后在配置文件中加入新的播放器类型,然后就可以利用反射工厂动态读取要支持的播放器类型了。是不是给人一种全身释然的感觉了,到这里,可能很多朋友觉得已经很完美了。如果说这样做,那也无可厚非。不过有的时候一想,我创建一种播放器就必须加入一个新的工厂来创建,这样到后来的维护量可能很大,尽管已经解耦了。是否有一种泛型工厂的方式来实现呢?我们继续探讨。
/// <summary>
/// 泛型视频播放器工厂
/// </summary>
public abstract class GenericVideoFactory<T> where T : VideoPlayer, new()
{
public virtual T CreateVideoPlayer()
{
return new T();
}
}
再次建立单元测试类:
[TestMethod]
public void TestGenericRealPlayerFactory()
{
var format = ConfigurationManager.AppSettings["RealPlayerFormat"];
var appPath = AppDomain.CurrentDomain.BaseDirectory;
var assembly = Assembly.LoadFrom(Path.Combine(appPath, "DesignPattern.FactoryMethodPattern.dll"));
//创建RealPlayerFactory
var type = "DesignPattern.FactoryMethodPattern.FactoryMethod.Generic" + format + "Factory";
var factory = (GenericVideoFactory<RealPlayer>)assembly.CreateInstance(type);
Assert.IsNotNull(factory);
var player = factory.CreateVideoPlayer();
Assert.IsNotNull(player);
Assert.IsTrue(player.GetType() == typeof(RealPlayer));
}
[TestMethod]
public void TestGenericMSMediaPlayerFactory()
{
var format = ConfigurationManager.AppSettings["MSMediaPlayerFormat"];
var appPath = AppDomain.CurrentDomain.BaseDirectory;
var assembly = Assembly.LoadFrom(Path.Combine(appPath, "DesignPattern.FactoryMethodPattern.dll"));
//创建RealPlayerFactory
var type = "DesignPattern.FactoryMethodPattern.FactoryMethod.Generic" + format + "Factory";
var factory = (GenericVideoFactory<MSMediaPlayer>)assembly.CreateInstance(type);
Assert.IsNotNull(factory);
var player = factory.CreateVideoPlayer();
Assert.IsNotNull(player);
Assert.IsTrue(player.GetType() == typeof(MSMediaPlayer));
}
现在所有的工厂创建动作移入到抽象的泛型工厂中,子类工厂只需要继承即可,这样从一定程度上减轻了创建实例的负担。
在实际项目中,可能还存在这样的场景:经常需要加工一批对象,这时如果按照Singleton-N模式来生产势必效率非常低下,最好有能独立进行生产的批量工厂。试想一下,除了造船、造卫星或造航空母舰等,很难想象造"啤酒"时要等待工厂进行一系列处理完后,才能产生一瓶啤酒,如果客户要求马上配送5000瓶时,要进行送货那简直就是一件短时间无法完成的事了。继续拿上面的视频播放器为例来说明吧,现在系统是支持了RealPlayer和MS MediaPlayer播放器了,但顾客有天要求他需要3000部支持RealPlayer的,2000部支持MS Media Player的播放器,此时我们就需要批量工厂了。我们来看看下面这张图代表批量生产的流程图。
从图中我们发觉共5个步骤,其中1,4,5都是我们原有工厂方法具有的过程,现在增加了2,3两个步骤,依据职责单一原则,我们通过建立辅助类来完成:
抽象生产指导者类(Director):它告诉客户端程序在保持VideoPlayer抽象播放器接口不变的情况下,生产某类播放器的数量,同时由于生产某类产品是由具体的创建工厂决定的,因此还需要告诉客户端当生产完3000部RealPlayer的播放器后,将创建工厂切换到MSMediaPlayerFactory继续生产2000部MSMediaPlayer。
步骤决策者类(Decision):它包含两个信息,当决策生产3000部RealPlayer播放器时,生产的数量(3000)和实际创建工厂(RealPlayerFactory)。
批量播放器集合类(VideoPlayerList):采用泛型集合类型,同时修改具体创建工厂的返回值,变为单独返回VideoPlayer的集合。
接下来,我们看看如何实现批量工厂。
/// <summary>
/// 视频播放器集合类
/// </summary>
public class VideoPlayerList
{
private IList<VideoPlayer> players = new List<VideoPlayer>();
#region 插入、删除、清除集合
public void Insert(VideoPlayer item)
{
players.Add(item);
}
public void Insert(VideoPlayer[] items)
{
if (items == null || items.Length == 0) return;
foreach (var item in items)
{
Insert(item);
}
}
public void Remove(VideoPlayer item)
{
players.Remove(item);
}
public void Clear()
{
players.Clear();
}
#endregion
//获取所有视频播放器的对象
public VideoPlayer[] Items
{
get
{
if (players == null || players.Count == 0) return null;
var items = new VideoPlayer[players.Count];
players.CopyTo(items, 0);
return items;
}
}
public int Count
{
get { return players.Count; }
}
//为了便于操作,这里重载运算符+
public static VideoPlayerList operator +(VideoPlayerList players, VideoPlayer[] items)
{
var result = new VideoPlayerList();
if (players != null && players.Count > 0)
{
result.Insert(players.Items);
}
if (items != null && items.Length > 0)
{
result.Insert(items);
}
return result;
}
public static VideoPlayerList operator +(VideoPlayerList source, VideoPlayerList target)
{
var result = new VideoPlayerList();
if (source != null && source.Count > 0)
{
result.Insert(source.Items);
}
if (target != null && target.Count > 0)
{
result.Insert(target.Items);
}
return result;
}
}
/// <summary>
/// 批量加工接口
/// </summary>
public interface IBatchFactory
{
VideoPlayerList CreateVideoPlayers(int quantity);
}
/// <summary>
/// 批量加工的抽象基类
/// </summary>
public abstract class BatchVideoPlayerFactory<T> : IBatchFactory
where T : VideoPlayer, new()
{
public virtual VideoPlayerList CreateVideoPlayers(int quantity)
{
if (quantity <= 0) return null;
var players = new VideoPlayerList();
for (var i = 0; i < quantity; i++)
{
players.Insert(new T());
}
return players;
}
}
//两个具体批量创建工厂
public class BatchRealPlayerFactory : BatchVideoPlayerFactory<RealPlayer> { }
public class BatchMSMediaPlayerFactory : BatchVideoPlayerFactory<MSMediaPlayer> { }
/// <summary>
/// 步骤决策者基类(包含创建工厂和数量)
/// </summary>
public abstract class Decision
{
protected IBatchFactory factory;
protected int quantity;
public Decision(IBatchFactory factory, int quantity)
{
this.factory = factory;
this.quantity = quantity;
}
public virtual IBatchFactory Factory
{
get { return this.factory; }
}
public virtual int Quantity
{
get { return this.quantity; }
}
}
//具体播放器决策类(此处可以通过配置文件配数量)
public class RealPlayerDecision : Decision
{
public RealPlayerDecision() : base(new BatchRealPlayerFactory(), 3000) { }
}
public class MSMediaPlayerDecision : Decision
{
public MSMediaPlayerDecision() : base(new BatchMSMediaPlayerFactory(), 2000) { }
}
/// <summary>
/// 生产指导者基类
/// </summary>
public abstract class Director
{
//步骤集合
protected IList<Decision> decisions = new List<Decision>();
//添加决策
public virtual void Insert(Decision decision)
{
if (decision == null || decision.Factory == null || decision.Quantity <= 0)
{
return;
}
decisions.Add(decision);
}
//以便客户端遍历决策集合
public virtual IEnumerable<Decision> Decisions
{
get { return decisions; }
}
}
/// <summary>
/// 视频播放器指导者
/// </summary>
public class VideoPlayerDirector : Director
{
public VideoPlayerDirector()
{
base.Insert(new RealPlayerDecision());
base.Insert(new MSMediaPlayerDecision());
}
//对外加工的入口
public VideoPlayerList Produce()
{
var players = new VideoPlayerList();
foreach (var decision in Decisions)
{
players += decision.Factory.CreateVideoPlayers(decision.Quantity);
}
return players;
}
}
我们建立单元测试:
[TestMethod]
public void TestBatchVideoPlayerFactory()
{
var director = new VideoPlayerDirector();
var players = director.Produce();//开始加工
Assert.AreEqual<int>(3000 + 2000, players.Count);
//验证前3000个播放器是RealPlayer
for (var i = 0; i < 3000; i++)
{
Assert.IsTrue(players.Items[i].GetType() == typeof(RealPlayer));
}
//验证后2000个播放器是MSMediaPlayer
for (var i = 3000; i < 5000; i++)
{
Assert.IsTrue(players.Items[i].GetType() == typeof(MSMediaPlayer));
}
}
到现在为止,利用工厂方法模式来面对简单工厂遇到的棘手问题都一一解决了。其实工厂模式是应对"某个对象"的创建工作,且该对象的子类无法在提前预知,能动态添加新类型和创建工厂而不影响其他子类的创建。朋友们也可以想一下如果我创建的实例有多个品牌以及多个型号(比如一个电脑主板可能来自惠普,也可能来自IBM,同时主板可能又有很多型号),又将实例从横向拉到纵向甚至多向,如果要解决这种多维度的对象创建工作,将会利用工厂模式创建其中的一部分维度,并组合成抽象工厂模式来解决。我会再下次讲解抽象工厂时详细解释,这里将本篇所需要的源代码提供以便参考。