工厂方法的目的很明确就是定义一个用来创建对象的接口,但是他不直接创建对象,而由他的子类来创建,这样一来就将创建对象的责任推迟到了该接口的子类中,创建什么类型的对象由子类来决定,而创建对象的时间由接口来定。因此该模式可以在如下几种情况下使用:1、a class can’t predict the class of objects it must create.2、a class wants its subclasses to specify the objects it creates.3、classes delegate responsibility to one of several helper subclasses,but do not know whick helper subclass is the delegate.在这里我们举一个面包房的例子:面包房的面包机好比一个抽象产品,它是一般的面包,那么我们可以通过特殊的加工工艺使它变成不同种类的面包比如:可以是中国式的面包或者是美国的面包等等。这些特殊种类的面包就是我们的具体产品。而做面包的人(也许是自己)就是Creator(创建者),我们使用我们的方法(工厂方法)来做我们想要的面包。这里的灵活性是不言而喻的,使用面包机可以让我们有更多的选择,只有我们在需要的时候才决定吃那一种面包,这不是很好的一件事情吗,如果没有面包机可能我们只能吃中国面包了。下面我们在举几个我们非常熟悉的FCL(.NET Framrwork Class Labrarly)中的例子,IEnumerable和Ienumerator就是一个Creator和一个Product。另一个例子是:WebRequest 是 .NET Framework 的用于访问 Internet 数据的请求/响应模型的抽象(在 Visual Basic 中为 MustInherit)基类。使用该请求/响应模型的应用程序可以用协议不可知的方式从 Internet 请求数据。在这种方式下,应用程序处理 WebRequest 类的实例,而协议特定的子类则执行请求的具体细节。请求从应用程序发送到某个特定的 URI,如服务器上的 Web 页。URI 从一个为应用程序注册的 WebRequest 子代列表中确定要创建的适当子类。注册 WebRequest 子代通常是为了处理某个特定的协议(如 HTTP 或 FTP),但是也可以注册它以处理对特定服务器或服务器上的路径的请求。(WebRequest摘自MSDN),这是一种带参数的工厂方法。在FCL里面工厂方法是一种最常见的模式应用。
以上据了一个简单的例子(面包),不知道是不是贴切,我不想重新描写一次该模式,但是再有必要的时候我会加以强调的,下面就用创建型模式的结构图来展现这个模式。
这次我想实现的就是书上的例子,首先我们需要看看我们的环境(上下文),我们为了实现一个迷宫的创建环境所以,这个迷宫的简单结构如下图所示:
为了实现工厂模式来创建迷宫我们有一下的类图,从图中可以看出Creator(MazeGame)里面有很多的工厂方法,用户可以通过它来得到不同的房子(组成迷宫的组件),但是也许我们不知道我们的MazeGame是要创建什么样的Room所以我们可以定义一些子类(现在有两个)用来实现不同的创建工作,Room是组成我们的迷宫的最常见的房子,MazeGame可以产生这样大的房子但是为了满足不同的要求我们可以定义一些特殊的房子比如:带炸弹的房子(是指墙上有炸弹)和可以施魔法的房子(可以对门施魔法)。这样我们可以不改变原来的接口,用MazeGame的子类来完成这些特殊的创建工作,就像下面的图中所表示的一样。
我们把创建的细节推迟到了子类去实现,这样很大程度上提高了程序的韧性,如果我们想创建一个新的房子或者新的门我们可以在不改变原来代码的情况下添加以个MazeGame子类来实现这个想法。但是我觉得这有一个问题为了创建一个新的房子我们必须创建一个新的子类来满足这个要求,这样看起来好像是一对一对的平行的样子。这样会使我们的Creator的子类越来越多,我们可以使用带参数的工厂方法减少这种类的繁殖,下面实现的代码并没有实现这种带参数的工厂方法,网友们可以自己实现。我们只要给MakeWall和MakeDoor传递一个参数来确定创建什么样的Wall和Door,不过我们需要在默认的MazeGame里相应的方法写重裁版本。
好了废话就不多说了,GOF说的比我清楚多了(其实我也是从他们那里学的J)。下面来实现我们的迷宫,代码如下:
首先实现我们的环境
using System;
using System.Collections;
// 该命名空间中是一些运行实例德的环境包括Maze、Door等等
namespace CommonObject{
// 所有的迷宫构件的基类里面有一个方法用来显示当前构件的信息
public class MapSite{
public virtual string Enter(){
return string.Empty;
}
}
// 墙是组成迷宫的构件之一,这里是一个很一般的墙(没有炸弹)
public class Wall : MapSite{
public override string Enter(){
return "This is a Wall.";
}
public Wall(){}
}
// 门也是迷宫的组成部分之一,这也是一个很普通的门(不能施魔法)
public class Door : MapSite{
public override string Enter(){
return "This is a Door.";
}
// 门是在两个房子之间的构件所以构造函数包含两个房子
// 说明是哪两个房子之间的门
public Door(Room roomFrom,Room roomTo){
this.m_Room1 = roomFrom;
this.m_Room2 = roomTo;
}
// 让我们有机会可以从门进入另一个房子,将会得到
// 另一个房子的引用
public Room OtherSideFrom(Room roomFrom){
if(this.m_Room1 == roomFrom)
return this.m_Room2;
else
return this.m_Room1;
}
// 这是一些私有的变量
private Room m_Room1;
private Room m_Room2;
// 描述门的状态默认所有的新门都是关的
private bool m_IsOpen = false;
// 提供一个公共访问的访问器
public bool IsOpen{
set{this.m_IsOpen = value;}
get{return this.m_IsOpen;}
}
}
// 房间是组成迷宫的基本单位
public class Room : MapSite
{
// 枚举类型表示房子的面
public enum Sides{
North = 0,
East,
South,
West
}
public override string Enter(){
return "This is a Room.";
}
// 构造函数,为了可以区分房间我们给每一个房间加一个标示
public Room(int roomNumber){
this.m_roomNumber = roomNumber;
}
// 设置房子的面,房子有4个面组成,因为我们现在不知道每个面
// 的具体类型(门?墙?)所以我们用MapSite类型。
public void SetSide(Sides side,MapSite sideMap){
this.m_side[(int)side] = sideMap;
}
// 得到指定的面,同样我们不知道得到的是哪一个面
// 所以我们用MapSite返回结构
public MapSite GetSide(Sides side){
return this.m_side[(int)side];
}
// 一些私有成员 房号
protected int m_roomNumber;
// 房子有4个面
protected const int m_Sides = 4;
// 用一个1维的MapSite数组存储未知类型的面(墙或门)
protected MapSite[] m_side = new MapSite[m_Sides];
}
// 带炸弹的房子
public class BombedRoom : Room{
public BombedRoom(int roomNumber) : base(roomNumber){}
}
// 带迷宫的房子
public class EnchantedRoom : Room{
public EnchantedRoom(int roomNumber,Spell spell) : base(roomNumber){}
}
// 这是一个特殊的墙--带炸弹的
public class BombedWall : Wall{}
// 这是一个可以施魔法的门
public class EnchantedDoor : Door{
public EnchantedDoor(Room roomFrom,Room roomTo) : base(roomFrom,roomTo){}
}
// 这就是我们的迷宫了
public class Maze{
private ArrayList m_Rooms = new ArrayList();
public Room RoomNumber(int roomNumber){
return (Room)this.m_Rooms[roomNumber];
}
public void AddRoom(Room room){
this.m_Rooms.Add(room);
}
}
// 魔法类
public class Spell{}
}
接下来是工厂方法的实现:
using System;
using System.Collections;
namespace FactoryMethod_Example
{
// 加入MazeContext
using CommonObject;
// 实现了一些工厂方法的Creator类
public class MazeGame
{
// 要返回给Client的对象
private Maze m_Maze = null;
// 一个访问器用来得到maze
public Maze Maze
{
get
{
if (this.m_Maze == null)
this.m_Maze = CreateMaze();
return this.m_Maze;
}
}
// 构造器
public MazeGame()
{
}
// 以下就是一些工厂方法创建迷宫的每个个构件
public virtual Maze MakeMaze()
{
return new Maze();
}
public virtual Room MakeRoom(int id)
{
return new Room(id);
}
public virtual Wall MakeWall()
{
return new Wall();
}
public virtual Door MakeDoor(Room room1, Room room2)
{
return new Door(room1, room2);
}
// 创建迷宫
public Maze CreateMaze()
{
Maze maze = MakeMaze();
// 创建门和房间
Room room1 = MakeRoom(1);
Room room2 = MakeRoom(2);
Door theDoor = MakeDoor(room1, room2);
// 将房间添加到迷宫里面
maze.AddRoom(room1);
maze.AddRoom(room2);
// 设置room1的面
room1.SetSide(Room.Sides.North, MakeWall());
room1.SetSide(Room.Sides.East, theDoor);
room1.SetSide(Room.Sides.South, MakeWall());
room1.SetSide(Room.Sides.West, MakeWall());
// 设置room2的面
room2.SetSide(Room.Sides.North, MakeWall());
room2.SetSide(Room.Sides.East, MakeWall());
room2.SetSide(Room.Sides.South, MakeWall());
room2.SetSide(Room.Sides.West, theDoor);
return maze;
}
}
// 创建带炸弹迷宫
public class BombedMazeGame : MazeGame{
public BombedMazeGame(){}
public override Wall MakeWall(){
return new BombedWall();
}
public override Room MakeRoom(int n){
return new BombedRoom(n);
}
}
// 创建带魔法的迷宫
public class EnchantedMazeGame : MazeGame{
private Spell m_spell;
public EnchantedMazeGame(Spell spell){
this.m_spell = spell;
}
public override Door MakeDoor(Room r1,Room r2){
return new EnchantedDoor(r1,r2);
}
public override Room MakeRoom(int n){
return new EnchantedRoom(n,this.m_spell);
}
}
}
上面的代码在.NET IDE里测试通过,上面说过了在FCL里面又很多的工厂方法的实现,如果想要继续学习和理解可以看看MSDN的帮助。希望我写的这些可以帮助你更好的理解工厂方法模式。如果有什么不正确的地方希望指出可以发邮件到wu_jian830@hotmail.com里,我将虚心学习,希望我们可以共同进步。