我的收藏

学习PHP设计模式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设计模式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方法。这样对外就可以用统一的方法操作这两种对象。

学习PHP设计模式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;

?>

用途总结:备忘模式使得我们可以保存某一时刻为止的信息,然后在需要的时候,将需要的信息恢复,就像游戏的保存和载入归档一样。

实现总结:需要一个备忘类来保存信息,被保存的类需要实现生成备忘对象的方法,以及调用备忘对象来恢复自己状态的方法。

学习PHP设计模式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();

?>

用途总结:适配器模式使得一个类可以同时使用两个基础类的功能,跳出了单纯继承的限制。有效的重用多各类。

实现总结:让新的类去继承一个基础类,然后通过新类的属性来存放其他类的对象,通过这些对象来调用其他类的方法。

学习PHP设计模式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();

?>

用途总结:中介者模式可以减少各个对象的通讯,避免代码相互关联。

实现总结:中介者模式比较灵活,一般只要有中介者类和需要被协调的类,具体设计看遇到的问题。