【转】PHP之星际设计模式上(转自lightsaber)
2010-12-10 11:14
302 查看
1-简单工厂模式 不熟悉面向对象的朋友,建议先看看用星际快速入门PHP面向对象编程 上次用星际争霸讨论了面向对象的基础知识,似乎面向对象能解决很多问题。 但是还会有很多问题,简单的靠类和对象解决不得太好。 比如如何根据玩家输入的内容(尽管可以转化为其他字符串),来确定要制造的兵种,玩家不会输入代码:new Marine()。 和星际一样,PHP也没有终极兵种,如果类和接口是兵种的话,那么设计模式就是你的战术和控制,它可以让你靠各种兵种的搭配获胜。 待解决的问题:在人族的兵营,我们靠相应玩家的输入来动态确定要造的兵种,假设是机枪兵和火焰兵。 思路:动态的根据传递的数据,新建相应的类的对象。 简单工厂模式示例: 我们把机枪兵类的代码放入一个文件,Marine.php,它的代码如下: <?php class Marine { //机枪兵攻击的方法 public function attack() { echo 'Marine attack'; } } ?> 我们把火焰兵类的代码放入一个文件,Firebat.php,它的代码如下: <?php class Firebat { //火焰兵攻击的方法 public function attack() { echo 'Firebat attack'; } } ?> 主文件中的内容如下: <?php //兵种制造器的类 class BarracksCreator { //制造兵种的方法 public create($createWhat) { //根据输入的参数,动态的把需要的类的定义文件载入 require_once($createWhat.'.php'); //根据输入的参数,动态的返回需要的类的对象 return new $createWhat; } } //新建一个兵种制造器对象 $creator = new BarracksCreator(); //靠接收参数制造一个火焰兵对象 $troop1 = $creator->create('Marine'); $troop1->attack(); //靠接收参数制造一个机枪兵对象 $troop2 = $creator->create('Firebat'); $troop2->attack(); ?> 用途总结:简单工厂模式可以将新建对象的任务进行封装,一旦需要增加新的返回类,只要修改负责新建对象的那部分代码。 实现总结:需要一个自动根据参数返回新建对象的工厂,比如上面兵种制造器BarracksCreator,使用的时候只需要将参数传递给他的生产方法create(),无需考虑具体的生产细节。 /***************************************************************/ 2-工厂方法模式 PHP手册上提到的工厂模式,其实是简单工厂模式。这里来讨论简单工厂模式的扩展:工厂方法模式。 待解决的问题:虽然简单工厂解决了动态返回不同类型对象的问题,但是实际情况当中,往往在新建一个对象的时候,需要做一些额外处理,比如制造机枪兵的时候需要判断水晶矿是否大于50,而制造火焰兵的时候需要同时判断水晶矿是否大于50和气矿大于25,还有是否建造了研究院。如果把这些代码全部放到工厂制造类里面,会使得制造类很臃肿,而且随着工厂生产的对象的种类越来越多,工厂制造类的代码会越来越难以维护。 思路:简单工厂模式中的工厂类(兵种制造器的类)保持不变,增加一个制造接口,定义一个实际制造对象的方法,然后定义各个具体制造不同对象的工厂,同时要求这些工厂执行这个制造接口,让这些工厂去实现实际制造对象的方法。 工厂方法模式示例: 我们把机枪兵类和制造机枪兵的类的代码放入一个文件,Marine.php,它的代码如下: <?php //机枪兵类 class Marine { //机枪兵攻击的方法 public function attack() { echo 'Marine attack'; } } //制造机枪兵的类,执行接口abstractCreator class MarineCreator implements abstractCreator { //实际制造机枪兵的方法 public function realCreate() { //如果水晶矿大于50,这里只是作为说明,因为并不存在ore这个变量,也不考虑水晶少于50的处理 if($ore>50) { return new Marine(); } } } ?> 我们把火焰兵类和制造火焰兵的类的代码放入一个文件,Firebat.php,它的代码如下: <?php //火焰兵类 class Firebat { //火焰兵攻击的方法 public function attack() { echo 'Firebat attack'; } } //制造火焰兵的类,执行接口abstractCreator class FirebatCreator implements abstractCreator //实际制造火焰兵的方法 public function realCreate() { //如果水晶矿大于50同时气矿大于25,并且研究院已经存在。这里只是作为说明,因为并不存在ore和gas和Academy变量,也不考虑资源不够时的处理 if($ore>50 && $gas>25 && Academy>1) { return new Firebat(); } } } ?> 主文件中的内容如下: <?php //各个具体工厂必须执行的接口 interface abstractCreator { //规定各个具体工厂要实现的方法 public function realCreate(); } //兵种制造器的类,也就是主工厂 class BarracksCreator { //制造兵种的方法 public create($createWhat) { //根据输入的参数,动态的把需要的类的定义文件载入 require_once($createWhat.'.php'); //根据输入的参数,动态的获取相应的具体工厂的类的名字 $creatorClassName = $createWhat.'Creator'; //新建具体工厂对象 $creator = new $creatorClassName; //用具体工厂来实际生产,然后返回需要的类的对象。因为它们都执行了接口abstractCreator,所以肯定实现了方法realCreate() return $creator->realCreate(); } } //新建一个兵种制造器对象 $creator = new BarracksCreator(); //靠接收参数制造一个火焰兵对象 $troop1 = $creator->create('Marine'); $troop1->attack(); //靠接收参数制造一个机枪兵对象 $troop2 = $creator->create('Firebat'); $troop2->attack(); ?> 用途总结:工厂方法模式将新建对象的任务将给对应的具体工厂类,不必因为某些生产的对象需要进行额外处理而修改对外的主工厂。 实现总结:需要接收参数的主工厂类,比如上面兵种制造器BarracksCreator,还需要声明具体制造方法的一个接口,比如上面abstractCreator,然后定义具体生产各个产品的具体工厂类,每个具体工厂类必须执行接口abstractCreator,这样他们就必须实现制造对象的方法,比如上面的realCreate()。使用的时候只需要将参数传递给主工厂类工厂的生产方法create(),然后由create()根据参数生成具体工厂类的对象,并调用具体工厂类realCreate()获取制造的产品对象并返回,对外界使用来说,只需调用主工厂类工厂进行生产。 说明:其实这篇文章内的工厂方法模式和有些文章写的不同,标准的工厂模式往往是用一个抽象类来代替上面的接口abstractCreator,然后让所有的具体工厂类来继承它,但使用的时候,由于抽象类不能实例化(新建它的对象),所以经常是代码中直接new FirebatCreator(),但是简单工厂模式可以解决直接new的问题,所以我这里将简单工厂模式和工厂方法模式一起使用,使这里的示例更加实用。同时由于PHP是单继承,而执行接口的数量是没有限制的,所以使用接口abstractCreator更加灵活。 /***************************************************************/ 3-抽象工厂模式 星际争霸是战略游戏,所以同样的兵种,敌我显示是不同的。 典型的就是鼠标的颜色,点中自己的物体的时候,鼠标颜色变成绿色,点中敌人的物体的时候,鼠标颜色变成红色。 还有就是每个物体的状态,点中自己的物体的时候,状态区显示完整的状态,点中敌人的物体的时候,状态区显示一部分信息。 我们假设只考虑鼠标和人族的运输船,玩家自己的运输船点中后状态区会显示里面装载的部队,而点中敌人的则不会显示里面是否装载部队。 这样我们就有四种对象:点中自己的鼠标,点中敌人的鼠标,自己的运输船状态,敌人的运输船状态。 如果用工厂方法模式,就要建立四个具体工厂(或者子工厂),这样的代码不便于维护和修改,因为我们以后要增加另一种情况:盟友。 待解决的问题:我们希望将这些对象联系起来,使得工厂的操作更加有逻辑性。 思路:既然我们通过自己和敌人来区分对象,那么统一归属的对象放入相同的具体工厂,每个具体工厂负责制造多种对象。 抽象工厂模式示例: <?php //四个产品类 //点中自己的物体时的鼠标 class mineMouse { //鼠标的颜色 $color = 'green'; } //点中敌人的物体时的鼠标 class enemyMouse { //鼠标的颜色 $color = 'red'; } //自己的运输船状态 class mineDropship { //显示装载的情况,假设2辆坦克 $loading = '2 tanks'; } //敌人的运输船状态 class enemyDropship { //不显示装载的情况 $loading = ''; } //主工厂类,也叫抽象工厂类 class abstractCreator { //根据参数分配工作到具体的工厂,并返回具体工厂对象 public function getCreator($belong) { //获取具体工厂的类名 $creatorClassName = $belong.'Creator'; //返回具体工厂对象 return new $creatorClassName(); } } //具体工厂必须执行的接口 interface productCreator { //制造方法,或者说根据参数返回产品(鼠标,运输船)的方法 public function creatProduct($productName); } //制造属于自己的物体的具体工厂,执行接口 class mineCreator implements productCreator { //根据参数生产并返回属于自己的产品 public function creatProduct($productName) { //获取产品的类名 $productClassName = 'mine'.$productName; //返回产品对象 return new $productClassName; } } //制造属于敌人的物体的具体工厂,执行接口 class enemyCreator implements productCreator { //根据参数生产并返回属于敌人的产品 public function creatProduct($productName) { //获取产品的类名 $productClassName = 'enemy'.$productName; //返回产品对象 return new $productClassName; } } //开始操作 //新建抽象工厂对象 $abstractCreator = new abstractCreator(); //根据归属,得到具体工厂对象,这里先演示敌人的 $realCreator1 = $abstractCreator->getCreator('enemy'); //让具体工厂对象生产鼠标对象 $product1 = $realCreator1->creatProduct('Mouse'); //让鼠标对象显示颜色,显示结果red echo $product1->color; //根据归属,得到另一个具体工厂对象,这里演示自己的 $realCreator2 = $abstractCreator->getCreator('mine'); //让具体工厂对象生产运输船 $product2 = $realCreator2->creatProduct('Dropship'); //让运输船对象显示装载对象,显示结果2 tanks,两辆坦克 echo $product2->loading; ?> 用途总结:抽象工厂模式将拥有相同属性的产品归类到同一个具体工厂,减少具体工厂的数量,操作的时候,可以理清职责。 实现总结:需要一个根据属性返回具体工厂对象的抽象工厂,比如上面abstractCreator,同时需要将各个产品的属性(自己的,敌人的)进行归类,根据属性建立各个具体工厂,每个具体工厂制造多个具有相同属性的不同产品(鼠标和运输船)。 /***************************************************************/ 4-单件和单态模式 星际争霸允许玩家作弊,当然这是在人和电脑对战的时候。而且作弊有个特点,比如快速建造,能量无限是对所有的玩家(包括电脑)都生效,如果关闭了作弊,对所有的玩家的作用都同时消失。 这也就是说如果我们把作弊状态作为一个类,他只能有一个对象。 待解决的问题:确保某个类只能有一个对象。 思路:把对外新建对象的权利都收回,包括new,clone。为了防止通过子类来覆盖父类的方法和成员,将类设置为final。用static成员来保存唯一的对象 单件模式示例: <?php //将类设置为final,禁止其他类继承它 final class cheat { //快速建造的生效状态,用private保护 private $fastBuild = false; //用static成员来保存唯一的对象 private static $instance; //设置快速建造的生效状态的方法,用public为了能够公开调用 public function setStatus($input) { //如果输入的秘籍正确,operation cwal是快速建造的秘籍 if($input === 'operation cwal') { //像开关一样,逆反状态 $this->fastBuild = !$this->fastBuild ; } } //读取快速建造的生效状态的方法,用public为了能够公开调用 public function getStatus() { return $this->fastBuild ; } //获取唯一对象的唯一方法 public function getInstance() { //如果对象没有被新建,则新建它 if(!isset(self::$instance)) { self::$instance = new cheat() ; } return self::$instance ; } //用private来禁止在本类以外new对象 private function __construct(){} //用private来禁止在本类以外clone对象 private function __clone(){} } //获取作弊对象的唯一办法 $cheatInstance = cheat::getInstance(); //现在输出为0(这和操作系统有关,有些可能输出false,最好用var_dump来代替echo) echo $cheatInstance->getInstance(); //输入秘籍 $cheatInstance->setInstance('operation cwal'); //现在输出为1(这和操作系统有关,有些可能输出true,最好用var_dump来代替echo) echo $cheatInstance->getInstance(); //再次输入秘籍,取消作弊 $cheatInstance->setInstance('operation cwal'); //现在输出为又变成0(这和操作系统有关,有些可能输出false,最好用var_dump来代替echo) echo $cheatInstance->getInstance(); ?> 星际里面有些特殊的秘籍,比如无敌和增加矿,这是仅仅对于人玩家产生作用,对电脑玩家没有作用(大家都无敌还怎么玩)。我们希望有个人类作弊的类来继承作弊类,而子类中新增一些秘籍,比如无敌。单件模式不允许继承,我们可以采用单态模式。 单态模式不是通过唯一对象来保持一致,它将相关的成员设置为static,这样即使存在很多个它的对象,但它们共享成员,保持状态的一致。同时也允许继承。 <?php //不使用final,允许继承 class cheat { //快速建造的生效状态,用private保护,同时设置static让所有的作弊对象共享 private static $fastBuild = false; //设置快速建造的生效状态的方法,用public为了能够公开调用 public function setStatus($input) { //如果输入的秘籍正确,operation cwal是快速建造的秘籍 if($input === 'operation cwal') { //像开关一样,逆反状态 self::$fastBuild = !self::$fastBuild ; } } //读取快速建造的生效状态的方法,用public为了能够公开调用 public function getStatus() { return self::$fastBuild ; } } //新增一个作弊对象 $cheatInstance1 = new cheat(); //现在输出为0(这和操作系统有关,有些可能输出false,最好用var_dump来代替echo) echo $cheatInstance1->getInstance(); //输入秘籍 $cheatInstance1->setInstance('operation cwal'); //现在输出为1(这和操作系统有关,有些可能输出true,最好用var_dump来代替echo) echo $cheatInstance1->getInstance(); //新增一个作弊对象 $cheatInstance2 = new cheat(); //现在也是输出为1,因为它们共享$fastBuild echo $cheatInstance2->getInstance(); ?> 作弊的子类可以新增一些成员,这里不再详述。 单件模式用途总结:确保某个类的对象的唯一性,常用来节省系统的资源,比如防止重复的数据库连接。 单件模式实现总结:收回所有新建对象的权利,把对象放入类的一个private成员中,仅提供一个对外的新建对象的方法,调用的时候判断对象是否已经新建 单态模式用途总结:确保某个类所有的对象的成员都一致,同时允许灵活的继承这各类。但相对单件模式而言,系统资源开销要大一些。 单态模式实现总结:把所有相关的成员设置为static。 /***************************************************************/ 5-模板模式 星际中的虫族部队有个特别的进化兵种,就是飞龙,飞龙可以变成空中卫士(天蟹)或者吞噬者(对空的)。另外还有口水兵可以进化变成地刺。 这三个变化过程都是类似的:变化的原部队消失,产生一个蛋或茧,孵化一会儿,蛋消失,新的部队产生。 如果我们把这三个进化独立开,就会产生重复的代码,冗余度增大了,所以我们要设法减少多余的代码。 待解决的问题:要经历同样的几个步骤,只是每个步骤的细节会有不同。 思路:做一个进化工程的框架,我们只要控制细节就可以了。 模板模式模式示例: <?php //进化的框架类,它是个抽象类 abstract class evolution { //框架方法,由它来实施各个步骤,用final禁止子类覆盖 final public function process($troop) { 生成一个蛋,参数为进化的部队 $egg = $this->becomeEgg($troop); 等待蛋孵化,参数为蛋 $this->waitEgg($egg); 孵化后产生新部队 return $this->becomeNew($egg); } 下面三个抽象方法,由具体子类来实现 abstract public function becomeEgg($troop); abstract public function waitEgg($egg); abstract public function becomeNew($egg); } 为了简单说明,这里用空中卫士(天蟹)的进化类来演示,地刺等的处理方法类似 //天蟹的进化类继承抽象进化类 class GuardianEvolution extends evolution { //实现生成一个蛋 public function becomeEgg($troop) { //销毁飞龙,返回一个蛋的对象的代码 } //等待蛋孵化 public function waitEgg($troop) { //等待几十秒钟的代码 } //孵化后产生新部队 public function becomeNew(($troop) { //销毁蛋,返回一个天蟹 } } //新建一个天蟹进化的对象 $e1 = new GuardianEvolution(); //让它调用父类的进化框架函数,自动完成三个步骤 $e1->process($sds); ?> 用途总结:模板模式可以将一系列的步骤自动化,同时又可以满足不同的细节变化。 实现总结:需要一个抽象类来包含框架函数,让具体的子类继承它,并实现所有的步骤。使用的时候只要调用框架函数就自动完成了。 /***************************************************************/ 6-正面模式 星际里面的战斗都是在地图上进行的,只要我们可以编辑地图,就可以创造一些新的战役。可是,星际里面的地图绘制相关的代码如果开放出来,估计大多数万家都看不懂,更不要说自己编辑地图了。 待解决的问题:在不了解地图代码的结构下,我们要让玩家自己编辑地图。 思路:对于玩家而言,他熟悉的是水晶矿,高地这些形状,他和系统通过鼠标交互。我们可以设计一个地图编辑器让玩家使用,而无需让他研究绘制地图的 细节代码。(实际上暴雪公司就是这样做的,很多玩家甚至暴雪内部人员都是用星际中的地图编辑器制作地图) 正面模式(Facade)示例: <?php //玩家的鼠标对象,记录鼠标在编辑其中的状态 class mouse { //鼠标所处的X轴坐标 public static $X; //鼠标当前能绘制的对象,比如水晶矿,河流等 public static $object; //鼠标所处的Y轴坐标 public static $Y; } //地图编辑器 class mapEdit { //绘制方法 public static function draw() { //根据鼠标对象的状态在地图上绘制各种东西 //如果是水晶矿 if(mouse::$object == "ore") { //调用水晶矿类的绘制方法,这个类定义在下面,这是真正的绘制,但玩家不必学习他的细节 ore::draw(); //如果是河流 }elseif(mouse::$object == "river"){ //调用河流类的绘制方法,这个类定义在下面,这是真正的绘制,但玩家不必学习他的细节 river::draw(); } } } //水晶矿类 class ore { //剩余的矿,以及其他属性,这里略过 public $remain; //绘制水晶矿 public static function draw() { //实际的绘制水晶矿的底层细节代码 } } //河流类 class river { //绘制河流 public static function draw() { //实际的绘制河流的底层细节代码 } } //玩家在地图编辑器上点击绘制对象列表上的水晶矿对象 mouse::$object = "ore"; //玩家移动鼠标 mouse::$X = 311; mouse::$Y = 126; //在地图上点击,表示绘制当前对象,也就是一个水晶矿 mapEdit::draw(); ?> 用途总结:正面模式让使用者集中于他所要进行的工作,而不必知道全部细节,或者说提供了一个容易使用的工具,同时屏蔽了底层细节,不必让使用者重新学习。 实现总结:需要一个类似上面地图编辑器的代码类,帮助玩家方便的进行操作。 /***************************************************************/ 7-观察者模式 当我们在星际中开地图和几家电脑作战的时候,电脑的几个玩家相当于结盟,一旦我们出兵进攻某一家电脑,其余的电脑会出兵救援。 那么如何让各家电脑知道自己的盟友被攻击了呢?并且自动做出反应? 待解决的问题:一旦某个电脑被我们进攻,其他电脑就获知,并且自动出兵救援。 思路:为电脑设置一些额外的观察系统,由他们去通知其他电脑。 观察者(Observer)模式示例: <?php //抽象的结盟类 abstract class abstractAlly { //放置观察者的集合,这里以简单的数组来直观演示 public $oberserverCollection; //增加观察者的方法,参数为观察者(也是玩家)的名称 public function addOberserver($oberserverName) { 以元素的方式将观察者对象放入观察者的集合 $this->oberserverCollection[] = new oberserver($oberserverName); } //将被攻击的电脑的名字通知各个观察者 public function notify($beAttackedPlayerName) { //把观察者的集合循环 foreach ($this->oberserverCollection as $oberserver) { //调用各个观察者的救援函数,参数为被攻击的电脑的名字,if用来排除被攻击的电脑的观察者 if($oberserver->name != $beAttackedPlayerName) $oberserver->help($beAttackedPlayerName); } } abstract public function beAttacked($beAttackedPlayer); } //具体的结盟类 class Ally extends abstractAlly { //构造函数,将所有电脑玩家的名称的数组作为参数 public function __construct($allPlayerName) { //把所有电脑玩家的数组循环 foreach ($allPlayerName as $playerName) { //增加观察者,参数为各个电脑玩家的名称 $this->addOberserver($playerName); } } //将被攻击的电脑的名字通知各个观察者 public function beAttacked($beAttackedPlayerName) { //调用各个观察者的救援函数,参数为被攻击的电脑的名字,if用来排除被攻击的电脑的观察者 $this->notify($beAttackedPlayerName); } } //观察者的接口 interface Ioberserver { //定义规范救援方法 function help($beAttackedPlayer); } //具体的观察者类 class oberserver implements Ioberserver { //观察者(也是玩家)对象的名字 public $name; //构造函数,参数为观察者(也是玩家)的名称 public function __construct($name) { $this->name = $name; } //观察者进行救援的方法 public help($beAttackedPlayerName) { //这里简单的输出,谁去救谁,最后加一个换行,便于显示 echo $this->name." help ".$beAttackedPlayerName."<br>"; } abstract public function beAttacked($beAttackedPlayer); } //假设我一对三,两家虫族,一家神族 $allComputePlayer = array('Zerg1', 'Protoss2', 'Zerg2'); //新建电脑结盟 $Ally = new Ally($allComputePlayer); //假设我进攻了第二个虫族 $Ally->beAttacked('Zerg2'); ?> 用途总结:观察者模式可以将某个状态的变化立即通知所有相关的对象,并调用对方的处理方法。 实现总结:需要一个观察者类来处理变化,被观察的对象需要实现通知所有观察者的方法。 |
相关文章推荐
- PHP之星际设计模式上(转自lightsaber)
- PHP之星际设计模式上(转自lightsaber)
- PHP之星际设计模式中(转自lightsaber)
- PHP之星际设计模式下(转自lightsaber)
- PHP之星际设计模式中(转自lightsaber)
- PHP之星际设计模式上(转自lightsaber)
- PHP之星际设计模式下(转自lightsaber)
- 14. 星际争霸之php设计模式--状态模式
- 15. 星际争霸之php设计模式--策略模式
- 16. 星际争霸之php设计模式--组合模式
- 17. 星际争霸之php设计模式--职责链模式
- 4. 星际争霸之php设计模式--工厂方法模式
- 18. 星际争霸之php设计模式--观察者模式
- 5. 星际争霸之php设计模式--抽象工厂模式
- 19. 星际争霸之php设计模式--迭代器模式
- 20. 星际争霸之php设计模式--适配器模式
- 6. 星际争霸之php设计模式--建造器模式
- 用星际学习PHP设计模式3-抽象工厂模式[转]
- 7. 星际争霸之php设计模式--中介者模式
- 8. 星际争霸之php设计模式--享元模式