15-状态模式
星际的一些兵种会有不止一种状态,比如坦克可以架起来,枪兵可以打兴奋剂,甚至还有一些被动的,比如被虫族女王喷洒绿色液体后,敌人的行动变慢。如果按照一般的思路,每次我们对一个小兵进行操作的时候,比如一辆坦克,我们都要用if判断他的状态,这样代码中会有很多的if,else或者swith。不过我们可以发现,我们需要的是他在某个状态下的行为,如果把这些行为按照状态封装起来,就可以减少大量的判断。待解决的问题:封装坦克的状态,让状态自己去控制行为。思路:把状态作为属性,兵种类本身只控制状态的变化,具体的行为由状态类定义。
状态(State)模式示例:<?php//坦克状态的接口interface TankState{ //坦克的攻击方法public function attack();}//坦克普通状态class TankState_Tank implements TankState{ //坦克的攻击方法public function attack(){ //这里简单的输出当前状态echo "普通状态";}}
//坦克架起来的状态class TankState_Siege implements TankState{ //坦克的攻击方法public function attack(){ //这里简单的输出当前状态echo "架起来了";}}
//坦克类class Tank{ //状态public $state;//坦克的攻击方法public function __construct(){ //新造出来的坦克当然是普通状态$this->state = new TankState_Tank();}//设置状态的方法,假设参数为玩家点击的键盘public function setState($key){ //如果按了sif($key = 's'){ $this->state = new TankState_Siege();}//如果按了telseif($key = 't'){ $this->state = new TankState_Tank();}}//坦克的攻击方法public function attack(){ //由当前状态自己来处理攻击$this->state->attack();}}//新造一辆坦克$tank = new Tank();//假设正好有个敌人路过,坦克就以普通模式攻击了$tank->attack();//架起坦克$tank->setState('s');//坦克再次攻击,这次是架起模式$tank->attack();?>
用途总结:状态模式可以将和状态相关的行为和属性封装,除了切换状态时,其它地方就不需要大量的判断当前状态,只要调用当前状态的方法等。实现总结:用一个接口规范状态类需要实现的方法,比如上面的TankState规定了attack()。把各个状态封装成类,将不同状态下的不同方法放入各自的状态类,比如上面的攻击方法,同时所有的状态执行接口。原来的事务类,比如上面的Tank类,只负责状态切换,一旦需要某一个方法的调用,只要交给当前状态就可以了。
/***************************************************************/
16-中介者模式
星际的升级系统做得比较平衡,不过由于不少兵种和建筑的制造都需要有相关的科技建筑,所以关系比较复杂。比如一个科学站造出来后,所有的飞机场都可以建造科技球了,但是一旦一个科学站被摧毁,就要看是否还有科学站,否则就得让所有的飞机场都不能造科技球。我们可以用上次说的观察者模式解决问题,不过由于星际里面的升级相关比较多,似乎比较麻烦。其实从实质来讲,任何升级一般只要知道某种建筑是否存在就行了,因此我们不必让他们多对多联系,设置一个中介者就行了。这就好像我们不管买什么东西,到超市就可以了,而厂家也只要和超市联系,不必和我们每个消费者直接接触。待解决的问题:不要让各个建筑互相联系,减少复杂程度。思路:设置中介者,每次遇到制造科技相关的东西,询问中介者。
中介者(Mediator)模式示例:<?php//中介者class Mediator{ //存放科技建筑的数量,为了简单说明,用静态属性,其实也可以让各个对象来处理public static $techBuilding;//根据参数$techBuildingName代表的建筑名称,返回是否存在相应的科技建筑,为了简单说明,用静态属性public static function isTechAllow ($techBuildingName){ //如果科技建筑数量大于零,就返回true,否则返回falsereturn self::$techBuilding[$techBuildingName]>0;}//一旦科技建筑造好了或者被摧毁,调用这个方法,参数$techBuildingName代表建筑名称,$add为布尔值,true表示增加(建造),false代表减少(摧毁)public static function changeTech ($techBuildingName, $add){ //建造if ($add){ //增加数量self::$techBuilding[$techBuildingName]++;}else { //减少数量self::$techBuilding[$techBuildingName]--;}}}//科技站类class ScienceFacility{ //构造方法public function __construct(){ Mediator::changeTech('ScienceFacility', true);}//析构方法public function __destruct(){ Mediator::changeTech('ScienceFacility', false);}}
//飞机场类class Starport{ //制造科技球的方法public function createScienceVessel (){ //询问中介者,决定是否能制造科技球echo Mediator::isTechAllow('ScienceFacility')?'可以制造科技球':'不能制造科技球';}}
//造一个科技站$scienceFacility1 = new ScienceFacility();//再造一个科技站$scienceFacility2 = new ScienceFacility();//造一个飞机场$starport = new Starport();//建造科技球,结果是能够$starport->createScienceVessel();//一个科技站被摧毁unset($scienceFacility1);//这时建造科技球,结果是能够,因为还有一个科技站$starport->createScienceVessel();//另一个科技站被摧毁unset($scienceFacility2);//这时建造科技球,结果是不行$starport->createScienceVessel();?>
用途总结:中介者模式可以减少各个对象的通讯,避免代码相互关联。实现总结:中介者模式比较灵活,一般只要有中介者类和需要被协调的类,具体设计看遇到的问题。
/***************************************************************/
17-适配器模式
星际的很多兵种,都有至少一项特殊技能。而且有些兵种的技能是相同的,比如虫族部队都会恢复血。如果按照一般的思路,把技能的操作和控制作为方法,放在每个兵种的定义类来实现,代码会重复,也不容易修改。那我们就会考虑用继承的办法,比如我们可以设计一个虫族的基类,里面有受伤后血恢复的方法。在设计刺蛇(Hydralisk,口水兵)的时候,我们可以让刺蛇类继承虫族基类。但是刺蛇是可以研发钻地的,而钻地不是刺蛇独有的功能,是虫族地面部队都有的特点,我们也要把钻地作为公共基类。问题出来了,我们不能同时让刺蛇类继承两个类,这是php不允许的。
待解决的问题:如何混合重用两个类,思路:继承一个类,把新建其中一个类的对象作为属性,然后通过这个属性来调用第二个类的方法。
适配器(Adapter)模式示例:<?php//虫族基类class Zerg{ //血public $blood;//恢复血的方法public function restoreBlood(){ //自动逐渐恢复兵种的血}}
//钻地的类class Burrow{ //钻地的方法public function burrowOperation(){ //钻地的动作,隐形等等echo '我钻地了';}}//刺蛇的类class Hydralisk extends Zerg{ //把一个属性来存放钻地对象public $burrow;//构造方法,因为php不允许默认值采用对象,所以通过初始化赋值给$burrowpublic function __construct(){ $this->burrow=new Burrow();}//钻地的方法public function burrowOperation(){ //调用钻地属性存放的对象,使用钻地类的方法$this->burrow->burrowOperation();}}//制造一个刺蛇$h1 = new Hydralisk();//让他钻地$h1->burrowOperation();?>
用途总结:适配器模式使得一个类可以同时使用两个基础类的功能,跳出了单纯继承的限制。有效的重用多各类。实现总结:让新的类去继承一个基础类,然后通过新类的属性来存放其他类的对象,通过这些对象来调用其他类的方法。
/***************************************************************/
18-备忘模式
我们在玩星际任务版或者单机与电脑对战的时候,有时候会突然要离开游戏,或者在出兵前面,需要存储一下游戏。那么我们通过什么办法来保存目前的信息呢?而且在任何时候,可以恢复保存的游戏呢?
待解决的问题:保存游戏的一切信息,如果恢复的时候完全还原。思路:建立一个专门保存信息的类,让他来处理这些事情,就像一本备忘录。为了简单,我们这里用恢复一个玩家的信息来演示。
备忘(Memento)模式示例:<?php//备忘类class Memento{ //水晶矿public $ore;//气矿public $gas;//玩家所有的部队对象public $troop;//玩家所有的建筑对象public $building;//构造方法,参数为要保存的玩家的对象,这里强制参数的类型为Player类public function __construct(Player $player){ //保存这个玩家的水晶矿$this->ore = $player->ore;//保存这个玩家的气矿$this->gas = $player->gas;//保存这个玩家所有的部队对象$this->troop = $player->troop;//保存这个玩家所有的建筑对象$this->building = $player->building;}}
//玩家的类class Player{ //水晶矿public $ore;//气矿public $gas;//玩家所有的部队对象public $troop;//玩家所有的建筑对象public $building;//获取这个玩家的备忘对象public function getMemento(){ return new Memento($this);}//用这个玩家的备忘对象来恢复这个玩家,这里强制参数的类型为Memento类public function restore(Memento $m){ //水晶矿$this->ore = $m->ore;//气矿$this->gas = $m->gas;//玩家所有的部队对象$this->troop = $m->troop;//玩家所有的建筑对象$this->building = $m->building;}}//制造一个玩家$p1 = new Player();//假设他现在采了100水晶矿$p1->ore = 100;//我们先保存游戏,然后继续玩游戏$m = $p1->getMemento();//假设他现在采了200水晶矿$p1->ore = 200;//我们现在载入原来保存的游戏$p1->restore($m);//输出水晶矿,可以看到已经变成原来保存的状态了echo $p1->ore;?>
用途总结:备忘模式使得我们可以保存某一时刻为止的信息,然后在需要的时候,将需要的信息恢复,就像游戏的保存和载入归档一样。实现总结:需要一个备忘类来保存信息,被保存的类需要实现生成备忘对象的方法,以及调用备忘对象来恢复自己状态的方法。
/***************************************************************/
19-组合模式
星际里面我们可以下载别人制作的地图,或者自己做地图玩。我们在选择玩哪张地图的时候,可以看到游戏列出当前地图包里面的地图或地图包的名字。虽然地图和地图包是通过文件和文件夹区分的,但是我们开发的时候,总希望能使用对象来进行抽象。那么对于地图和地图包这两个相关的对象,我们能不能简化他们之间的区别呢?
待解决的问题:尽量是调用这两种对象的代码一致,也就是说很多场合不必区分到底是地图还是地图包。思路:我们做一个抽象类,让地图类和地图包类继承它,这样类的很多方法的名称一样。
组合(Composite)模式示例:<?php//抽象地图类abstract class abstractMap{ //地图或地图包的名称public $name;//构造方法public function __construct($name){ $this->name = $name;}//地图或地图包的名称,地图对象没有子对象,所以用空函数,直接继承public function getChildren(){}//添加子对象,地图对象没有子对象,所以用空函数,直接继承public function addChild(abstractMap $child){}//显示地图或地图包的名称public function showMapName(){ echo $this->name."<br>";}//显示子对象,地图对象没有子对象,所以用空函数,直接继承public function showChildren(){}}
//地图类,继承抽象地图,这里面我们暂且使用抽象地图的方法class Map extends abstractMap{
}//地图包类,继承抽象地图,这里面我们就需要重载抽象地图的方法class MapBag extends abstractMap{ //子对象的集合public $childern;//添加子对象,强制用abstractMap对象,当然地图和地图包由于继承了abstractMap,所以也是abstractMap对象public function addChild(abstractMap $child){ $this->childern[] = $child;}//添加子对象public function function showChildren(){ if (count($this->childern)>0){ foreach ($this->childern as $child){ //调用地图或包的名称$child->showMapName();}}}}
//新建一个地图包对象,假设文件夹名字为Allied,这个大家可以看看星际的地图目录,真实存在的$map1 = new MapBag('Allied');//新建一个地图对象,假设文件名字为(2)Fire Walker(也是真实的)$map2 = new Map('(2)Fire Walker');//接下去可以看到组合模式的特点和用处。//假设后面的代码需要操作两个对象,而我们假设并不清楚这两个对象谁是地图,谁是地图包//给$map1添加一个它的子对象,是个地图,(4)The Gardens$map1->addChild(new Map('(4)The Gardens'));//展示它的子对象$map1->showChildren();//给$map2添加一个它的子对象,是个地图,(2)Fire Walker,这里不会报错,因为地图继承了一个空的添加方法$map2->addChild(new Map('(2)Fire Walker'));//展示它的子对象,也不会出错,因为地图继承了一个空的展示方法$map2->showChildren();?>
用途总结:组合模式可以对容器和物体(这里的地图包和地图)统一处理,其他代码处理这些对象的时候,不必过于追究谁是容器,谁是物体。这里为了简化说明,没有深入探讨,其实组合模式常常用于和迭代模式结合,比如我们可以用统一的方法(就像这里的showChildren方法),获取地图包下所有的地图名(包括子目录)实现总结:用一个基类实现一些容器和物体共用的方法,比如上面的abstractMap,然后让容器和物体类继承基类。由于各自的特性不同,在容器和物体类中重载相应的方法,比如addChild方法。这样对外就可以用统一的方法操作这两种对象。
/***************************************************************/
20-桥接模式
在面向对象设计的时候,我们一般会根据需要,设计不同的类。但是如果两个类需要类似的功能的时候,我们就会遇到问题,到底重用还是重写。更复杂的是,如果一些功能需要临时转换就麻烦了,比如星际里面的虫族,地面部队可以钻到地下,然后隐形。但是小狗在地下不能动,而地刺可以攻击。尽管可以设计不同的类,但问题是玩家可以看到自己的部队埋在地下(一个洞),而敌人看不到。这涉及功能的切换,而且星际里面有很多探测隐形的东西,这样就更频繁了。
待解决的问题:我们要临时切换一些功能的实现方式,而且在此基础上不同的调用类又有所不同。思路:将钻地区分两种实现,不同的部队类在它的基础上进一步扩展。
桥接(Bridge)模式示例:<?php//虫族的基础类class Zerg{ //实现钻地的基本对象public $imp;//负责切换钻地基本对象的方法public function setImp($imp){ $this->imp = $imp;}//部队的钻地方法,可以扩展基本对象的钻地public function underground(){ $this->imp->underground();}}//小狗的类class Zergling extends Zerg{ //调用基本的钻地方法,然后实现扩展,这里简单的echopublic function underground(){ parent::underground();echo '小狗不能动<br>';}} //地刺的类class Lurker extends Zerg{ //调用基本的钻地方法,然后实现扩展,这里简单的echopublic function underground(){ parent::underground();echo '地刺能够进行攻击<br>';}} //钻地的基本接口interface Implementor{ //基本的钻地方法public function underground();} //隐形钻地的基本类class InvisibleImp implements Implementor{ //基本的钻地方法public function underground(){ echo '隐形了,什么也看不到<br>';}}
//不隐形钻地的基本类,比如玩家自己看到的或被探测到的class VisibleImp implements Implementor{ //基本的钻地方法public function underground(){ echo '地上一个洞<br>';}} //造一个小狗$z1 = new Zergling();//玩家把它埋入前沿阵地,假设此时有敌人经过,代码进行切换$z1->setImp(new InvisibleImp());//敌人看不到小狗,但是小狗也不能进攻$z1->underground();//造一个地刺$l1 = new Lurker();//玩家把它埋入前沿阵地,假设此时有敌人经过,代码进行切换$l1->setImp(new InvisibleImp());//敌人看不到地刺,但是地刺能攻击敌人$l1->underground();//敌人急了,马上飞过来一个科技球,代码进行切换$l1->setImp(new VisibleImp());//敌人看到地刺了,地刺继续攻击敌人$l1->underground();?>
用途总结:桥接模式可以将基本的实现和具体的调用类分开,调用类可以扩展更复杂的实现。实现总结:需要一些基本执行类,实现基本的方法,比如上面的两个钻地类。同时我们可以设计多个不同的扩展调用类,将基本的功能扩展,比如地刺和小狗就进一步实现了不同的在地下的行为。