GOF常读常新,相信下次再来回顾时,理解又会更深一层了。加油。
当我阅读创建型模式时,我始终这样提醒着自己:抓住变化点,隔离之,区分哪里是变化的,哪里是相对不变的。
创建型模式解决的即是new的问题,先看下面的例子,理解new所带来的麻烦。直接使用GOF上面的例子来说明,类似Room,Door的说明就不再重复了。
Maze* MazeGame::CreateMaze() { Maze* aMaze = new Maze; Room* r1 = new Room(1); Room* r2 = new Room(2); Door* theDr = new Door(r1,r2); ... }
当需要新的类型的Room1,Door3时,我们可以这样修改CreateMaze
Maze* MazeGame::CreateMaze() { Maze* aMaze = new Maze; Room* r1 = new Room1(1); Room* r2 = new Room2(2); Door* theDr = new Door3(r1,r2); ... }
此时希望使用子类Room4时,我们同样需要修改CreateMaze才能适应这样的变化。即使在CreateMaze里面加个switch case,并传参以决定new什么样的类型,当Room扩展出新的子类时,switch case语句同样需要修改。更重要的是,CreateMaze可能会很复杂,它依赖一个算法来创建迷宫,而且创建规则是与具体的类型无关的。直接使用new使得CreateMaze函数依赖了具体的类名。使得每次变化,都需要来修改CreateMaze。
new所带来的麻烦,你看到了吗?new即是程序中的变化点
既然变化点找到了,然后呢。照之前所说,隔离之,提炼出接口,达到依赖接口(MakeRoom(...)),也不依赖具体实现的目的(Room,Room1)。
class MazeFactory { public: MazeFactory(); virtual Maze* MakeMaze() const { return new Maze; } virtual Room* MakeRoom() const { return new Room(); } virtual Door* MakeDoor(Room* r1, Room* r2) const { return new Door(r1, r2); } ... }
而此时的CreateMaze()可以是这样的
Maze* MazeGame::CreateMaze(MazeFactory& factory) { Maze* aMaze = factory.MakeMaze(); Room* r1 = factory.MakeRoom(1); Room* r2 = factory.MakeRoom(2); Door* theDr = factory.MakeDoor(r1,r2); ... }
在CreateMaze只出现了MakeMaze,MakeRoom之类的接口,并没有具体Room的类型。当有新版本的NewRoom或NewDoor的时候,派生一个MazeFactory的子类NewMazeFactory(开放扩展,关闭修改原则):
class NewMazeFactory : public MazeFactory { public: NewMazeFactory(); virtual Room* MakeRoom() const { return new NewRoom(); } virtual Door* MakeDoor(Room* r1, Room* r2) const { return new NewDoor(r1, r2); } ... }
CreateMaze保持不变,而实际new出来的Room已经是新的NewRoom了。
但是,CreateMaze出现了一个新的东西MazeFactory。。。看看main函数的情况先
int main(void) { Maze* aMaze = CreateMaze(new NewMazeFactory); ... }
只需要传递不同的工厂类对象,就能创建出不同的Room,Door了。每一个工厂子类都可重写相关接口,也就是说每一个子类,都可以有一套自己的实现,而这一套实现是相关联的。如MakeRoom返回NewRoom,MakeDoor返回NewDoor。这也就是“提供一个创建一系列相关或相互依赖对象的接口”。
理一下思路,例子中各元素与图中的对应关系,以及变与不变
CreateMaze:Client
MazeFactory:AbstractFactory
NewMazeFactory:ConcreteFactory1
Room:ProductA2
Door:ProductA1
main函数在图中并没有体现出来,它是作为Client的用户,是可变的(new不同的工厂出来)。Client是相对不变的,它仅依赖着MazeFactory接口。
GOF中这样说明抽象工厂的适用情况:
1.一个系统要独立于它的产品的创建、组合和表示时。(CreateMaze有自己的创建规则,它不关心具体的创建。)
2.一个系统要由多个产品系列中的一个来配置时。(MazeFactory的多个接口表示一系列相关的产品)
3.当你要强调一系列相关的产品对象的设计以便进行联合使用时。
4.当你提供一个产品类库,而只想显示它们的接口而不是实现时。(CreateMaze依赖MazeFactory的接口,而不是实现。)