3.创建型模式

创建型模式抽象了实例化过程。他们帮助一个系统独立于如何创建、组合和表示它的那些对象。一个类创建型模式使用继承改变被实例化的类,而一个对象创建型模式将实例化委托给另外一个对象。

在这些模式中有两个不断出现的主旋律,1.它们都将关于该系统使用哪些具体的类的信息封装起来;2.他们隐藏了这些类的实例是如何被创建和放在一起的。

因为创建式模式紧密相关,我们将所有5个模式一起研究他们的相似点和相异点。用一个通用的例子——为一个电脑游戏创建一个迷宫,来说明它们的实现。我们将一个迷宫定义为一系列房间,一个房间知道它的邻居,可能的邻居要么是另一个房间,要么是一堵墙,或者是到另一个房间的一扇门。

我们使用C + +中的枚举类型Direction来指定房间的东南西北:

enum Direction {North, South, East, West};

类MapSite是所有迷宫组件的公共抽象类。为简化例子,MapSite仅定义了一个操作Enter,它的含义决定于你在进入什么。如果你进入一个房间,那么你的位置会发生改变。如果你试图进入一扇门,那么这两件事中就有一件会发生:如果门是开着的,你进入另一个房间。如果门是关着的,那么你就会碰壁。


image.png
class MapSite {
     public:
            virtual void Enter() = 0;
}

Enter为更加复杂的游戏操作提供了一个简单基础。例如,如果你在一个房间中说“向东走”,游戏只能确定直接在东边的是哪一个MapSite并对它调用Enter。特定子类的Enter操作将计算出你的位置是发生改变,还是你会碰壁。在一个真正的游戏中,Enter可以将移动的游戏者对象作为一个参数。
Room是MapSite的一个具体的子类,而 MapSite定义了迷宫中构件之间的主要关系。Room有指向其他MapSite对象的引用,并保存一个房间号,这个数字用来标识迷宫中的房间。

class Room: public MapSite {
      public:
          Room(int roomNo);
          MapSite* GetSite(Direction) const;
          void SetSite(Direction MapSite*);
          virtual void Enter();
     private:
          MapSite* _sides[4];
          int _roomNumber;
}

下面的类描述了一个房间的每一面所出现的墙壁或门:

class Wall: public MapSite {
      public:
            wall();
            virtual void Enter();
};

class Door: public MapSite {
    public:
        Wall();
        virtual void Enter();
};

class Door: public MapSite {
     public:
          Wall();
          virtual void Enter();
} ;

class Door: public MapSite {
      public:
           Door(Room* = 0, Room* = 0);
           virtual void Enter();
           Room* OtherSideFrom(Room*);
      private:
          Room* _room1;
          Room* _room2;
          bool  _isOpen;
}

我们不仅需要知道迷宫的各部分,还要定义一个用来表示房间集合的Maze类。用RoomNo操作和给定的房间号,Maze就可以找到一个特定的房间。

class Maze {
    public: 
        Maze();
        void AddRoom(Room*);
        Room* RoomNo(int) const;
    private:
        //...
}

RoomNo可以使用线形搜索、hash表、甚至一个简单数组进行一次查找。但我们在此处并不考虑这些细节,而是将注意力集中于如何指定一个迷宫对象的构件上。
我们定义的另一个类是MazeGame,由它来创建迷宫。一个简单直接的创建迷宫的方法是使用一系列操作将构件增加到迷宫中,然后连接它们。例如,下面的成员函数将创建一个迷宫,这个迷宫由两个房间和它们之间的一扇门组成:

Maze* MazeGame::CreateMaze() {
      Maze* aMaze = new Maze;
      Rom*  r1 = new Room(1);
      Rom*  r2 = new Room(2);
      Door* theDoor = new Door(r1, r2);
      
      aMaze->AddRoom(r1);
      aMaze->AddRoom(r2);
     
      r1->SetSide(North,  new Wall);
      r1->SetSide(East,  theDoor);
      r1->SetSide(South,  new Wall);
      r1->SetSide(West,  new Wall);
      
     r2->SetSide(North,  new Wall);
     r2->SetSide(East,  new Wall);
     r2->SetSide(South,  new Wall);
     r2->SetSide(West,  theDoor);
}

考虑到这个函数所做的仅是创建一个有两个房间的迷宫,它是相当复杂的。显然有办法使它变得更简单。例如,Room的构造器可以提前用墙壁来初始化房间的每一面。但这仅仅是将代码移到了其他地方。这个成员函数真正的问题不在于它的大小而在于它不灵活。它对迷宫的布局进行硬编码。改变布局意味着改变这个成员函数,或是重定义它 — 这意味着重新实现整个过程 — 或是对它的部分进行改变 — 这容易产生错误并且不利于重用。

创建型模式显示如何使得这个设计更灵活,但未必会更小。特别是,它们将便于修改定义一个迷宫构件的类。假设你想在一个包含(所有的东西)施了魔法的迷宫的新游戏中重用一个已有的迷宫布
局。施了魔法的迷宫游戏有新的构件,像DoorNeedingSpell,它是一扇仅随着一个咒语才能被锁上和打开的门;以及 EnchantedRoom,一个可以有不寻常东西的房间,比如魔法钥匙或是咒语。你怎样才能较容易的改变CreateMaze以让它用这些新类型的对象创建迷宫呢?

这种情况下,改变的最大障碍是对被实例化的类进行硬编码。创建型模式提供了多种不同方法从实例化它们的代码中除去对这些具体类的显式引用:

• 如果CreateMaze调用虚函数而不是构造器来创建它需要的房间、墙壁和门,那么你可以创建一个MazeGame的子类并重定义这些虚函数,从而改变被例化的类。这一方法是Factory Method(3 . 3)模式的一个例子。
• 如果传递一个对象给CreateMaze作参数来创建房间、墙壁和门,那么你可以传递不同的参数来改变房间、墙壁和门的类。这是 Abstract Factory(3 . 1)模式的一个例子。
• 如果传递一个对象给CreateMaze,这个对象可以在它所建造的迷宫中使用增加房间、墙壁和门的操作,来全面创建一个新的迷宫,那么你可以使用继承来改变迷宫的一些部分或该迷宫被建造的方式。这是Builder(3 . 2)模式的一个例子。
• 如果CreateMaze由多种原型的房间、墙壁和门对象参数化,它拷贝并将这些对象增加到迷宫中,那么你可以用不同的对象替换这些原型对象以改变迷宫的构成。这是 Prototype(3 . 4)模式的一个例子。
剩下的创建型模式,Singleton(3 . 5),可以保证每个游戏中仅有一个迷宫而且所有的游戏对象都可以迅速访问它 — 不需要求助于全局变量或函数。Singleton也使得迷宫易于扩展或替换,且不需变动已有的代码。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,884评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,347评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,435评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,509评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,611评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,837评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,987评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,730评论 0 267
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,194评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,525评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,664评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,334评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,944评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,764评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,997评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,389评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,554评论 2 349

推荐阅读更多精彩内容

  • 设计模式汇总 一、基础知识 1. 设计模式概述 定义:设计模式(Design Pattern)是一套被反复使用、多...
    MinoyJet阅读 3,922评论 1 15
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,598评论 18 399
  • 平生相交至切者,唯有杜康与周公。 桃花开时难期颐,桃花酿里觅知音。 黄钟毁弃斯文乱,弃置残生与蛮夷。 谁倒南柯尽虚...
    东吴思王阅读 139评论 0 0
  • 涟漪湖里舟中钓, 岸边芳菲俏。 暖阳歌里醉年关, 曲曲玲珑妙。 且随风去、 谁家窈窕? 寄捎福来到。 星光借我晚来...
    點下阅读 143评论 0 3