PHP之星际设计模式下(转自lightsaber)

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)
	{
		//如果按了s
		if($key = 's')
		{
			$this->state = new TankState_Siege();
		}
		//如果按了t
		elseif($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,否则返回false
		return 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不允许默认值采用对象,所以通过初始化赋值给$burrow
	public 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
{
	//调用基本的钻地方法,然后实现扩展,这里简单的echo
	public function underground()
	{
		parent::underground();
		echo '小狗不能动<br>';
	}
} 

//地刺的类
class Lurker extends Zerg
{
	//调用基本的钻地方法,然后实现扩展,这里简单的echo
	public 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();
?>

用途总结:桥接模式可以将基本的实现和具体的调用类分开,调用类可以扩展更复杂的实现。
实现总结:需要一些基本执行类,实现基本的方法,比如上面的两个钻地类。同时我们可以设计多个不同的扩展调用类,将基本的功能扩展,比如地刺和小狗就进一步实现了不同的在地下的行为。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值