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();
?>
用途總結:橋接模式可以將基本的實現和具體的調用類分開,調用類可以擴展更復雜的實現。
實現總結:需要一些基本執行類,實現基本的方法,比如上面的兩個鉆地類。同時我們可以設計多個不同的擴展調用類,將基本的功能擴展,比如地刺和小狗就進一步實現了不同的在地下的行為。
總結
以上是生活随笔為你收集整理的PHP之星际设计模式下(转自lightsaber)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: IPSEC非单播流量处理
- 下一篇: Cisco三层交换机DHCP中继简单配置