《设计模式》学习笔记(4)——抽象工厂模式(Abstract Factory)

1、意图
    提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。

2、别名
    Kit

3、适用性
    以下情况下可以使用此模式:

    • 一个系统不应当依赖于产品类实例如何被创建、组合和表达的细节,这对于所有形态的工厂模式都是重要的。

    • 这个系统有多于一个的产品族,而系统只消费其中某一产品族。

    • 同属于同一个产品族的产品是在一起使用的,这一约束必须在系统的设计中体现出来。

    • 系统提供一个产品类的库,所有的产品以同样的接口出现,从而使客户端不依赖于实现。

4、从上一篇的例子引申出来的一个问题:
    上一篇我们说到了使用硬编码的方法创建一个迷宫。这次我们需要将上面的代码复用,来创建一个被施了魔法的迷宫,这个施了魔法的迷宫由施过魔法的房间,需要符咒才能通过的门以及普通的墙壁构成。那么我们来看抽象工厂是如何方便解决这个问题的。

5、例子的结构图示、代码及注释 
       
  
    在这里,构建迷宫的元素在MazeFactory中创建,对应的代码如下:

namespace My.Reading.DesignPatterns.AbstractFactory
{
    
public class MazeFactory
    
{
        
public MazeFactory()
        
{

        }


        
public virtual Maze MakeMaze()
        
{
            
return new Maze();
        }


        
public virtual Room MakeRoom(int roomNo)
        
{
            
return new Room(roomNo);
        }


        
public virtual Wall MakeWall()
        
{
            
return new Wall();
        }


        
public virtual Door MakeDoor(Room r1, Room r2)
        
{
            
return new Door(r1, r2);
        }

    }

}


注意这里并没有将MazeFactory声明为abstract,因为在这个例子中,MazeFactory既作为AbstractFactory,EnchantedMazeFactory就是继承自它;同时又作为ConcreteFactory,它仍然需要创建普通的Maze对象。那么同样它的派生类的代码如下:

namespace My.Reading.DesignPatterns.AbstractFactory
{
    
public class EnchantedMazeFactory : MazeFactory
    
{
        
public EnchantedMazeFactory()
        
{

        }


        
public override Room MakeRoom(int roomNo)
        
{
            
return new EnchantedRoom(roomNo);
        }


        
public override Door MakeDoor(Room r1, Room r2)
        
{
            
return new DoorNeedingSpell(r1, r2);
        }

    }

}


由于Wall使用的仍然是普通的Wall,因此没有必要覆盖抽象工厂的虚方法。同时为了方便,没有使用EnchantedMaze而仍然使用Maze,只是在添加Room和Door的时候添加的是EnchantedRoom和DoorNeedingSpell。

Maze由于在上一篇已经定义过了,我们不需要改变原有的代码,我们现在只需要将与原来不同的类EnchantedRoom和DoorNeedingSpell重新定义。代码如下:

namespace My.Reading.DesignPatterns.AbstractFactory
{
    
public class DoorNeedingSpell : Door
    
{
        
public DoorNeedingSpell(Room r1, Room r2) : base(r1, r2)
        
{

        }


        
public override void Enter()
        
{
            
throw new NotImplementedException();
        }


        
public override void ShowSelfInformation()
        
{
            Console.WriteLine(
"I am the door that needs spell in namespace AbstractFactory!");
        }

    }


    
public class EnchantedRoom : Room
    
{
        
public EnchantedRoom(int roomNo) : base(roomNo)
        
{

        }


        
public override void Enter()
        
{
            
throw new NotImplementedException();
        }


        
public override void ShowSelfInformation()
        
{
            Console.WriteLine(
"I am the enchanted room in namespace AbstractFactory!");
        }

    }

}


那么,现在我们使用工厂作为参数来构建迷宫。如果我们使用普通迷宫,则传入创建普通迷宫的工厂;如果我们使用施了魔法的迷宫,则传入创建施了魔法魔法的迷宫的工厂。

namespace My.Reading.DesignPatterns.AbstractFactory
{
    
public class MazeGame
    
{
        
public MazeGame()
        
{

        }


        
public Maze CreateMaze(MazeFactory factory)
        
{
            Maze aMaze 
= factory.MakeMaze();
            Room r1 
= factory.MakeRoom(1);
            Room r2 
= factory.MakeRoom(2);
            Door theDoor 
= factory.MakeDoor(r1, r2);

            aMaze.AddRoom(r1);
            aMaze.AddRoom(r2);

            r1.SetSide(Direction.North, factory.MakeWall());
            r1.SetSide(Direction.East, theDoor);
            r1.SetSide(Direction.South, factory.MakeWall());
            r1.SetSide(Direction.West, factory.MakeWall());

            r2.SetSide(Direction.North, factory.MakeWall());
            r2.SetSide(Direction.East, factory.MakeWall());
            r2.SetSide(Direction.South, factory.MakeWall());
            r2.SetSide(Direction.West, theDoor);

            
return aMaze;
        }

    }

}

这样我们便避开了使用硬编码的方法,使得程序更容易扩展。

6、实现抽象工厂的一些有用技术:
    1)将工厂作为单件:一个应用中一般每个产品系列只需一个ConcreteFactory,因此工厂最好实现为一个Singleton。
    2)创建产品。抽象工厂仅仅声明了创建产品的接口,真正创建是由子类实现。那么通常有可能为每个产品定义一个工厂方法。但如果是有多个可能的产品系列,具体工厂也可以使用Phototype模式来实现。其实这样的创建产品主要是为了削弱抽象工厂对产品族的约束。
 
PS:原文是用C++描述的,很多地方只是示意性的代码。因此有些地方可能会有错误,欢迎指正。

此文章对应的代码下载:点击此处下载
posted @ 2005-04-11 16:01    阅读(1386)  评论(1编辑  收藏  举报