我的收藏

学习PHP设计模式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类,只负责状态切换,一旦需要某一个方法的调用,只要交给当前状态就可以了。

学习PHP设计模式14-迭代器模式

星际的任务关一般会有这样的设定:一开始电脑的农民不采矿,如果战斗打响,或者玩家造出第一个兵,电脑的农民开始采矿。

我们自然会想到把电脑的农民放到一个数组,然后一旦玩家造兵,或者战斗打响,把这个数组循环,让里面的农民采矿。

但问题出来了,由于每个任务的设定会有所不同,我们总希望任务的开发比较方便,而且容易修改(一旦发现bug)。

何况有些任务不是农民采矿,而是电脑出兵攻击玩家。

那么过多的固定细节(用数组存放)以及依赖细节(对数组循环),将使得代码的关联性变得很高。

待解决的问题:把循环处理的事务变的抽象。

思路:关键是对农民的循环,用数组处理只是一种方式,我们考虑抽象的数组,而不是具体的数组。

迭代器(Iterator)模式示例:

<?php

//聚集接口,意思是所有电脑的农民都聚集在这个类里面

interface IAggregate

{

  //让具体的聚集类实现的,获取使用的迭代器的方法

  public function createIterator();

}

//具体的聚集类

class ConcreteAggregate implements IAggregate

{

  //存放农民的数组,注意可以不用数组来处理,看完所有的代码就知道了

  public $workers;

  //增加元素的方法,这里元素就是农民

  public function addElement($element)

  {

    $this->workers[] = $element;

  }

  //获取元素的方法

  public function getAt($index)

  {

    return $this->workers[$index];

  }

  // 获取元素的数量的方法

  public function getLength()

  {

    return count($this->workers);

  }

  // 获取迭代器的方法

  public function createIterator()

  {

    return new ConcreteIterator($this);

  }

}

//迭代器接口,注意php5有个内置的接口叫Iterator,所以这里我们改成IIterator

interface IIterator

{

  //是否元素循环完毕

  public function hasNext();

  //返回下一个元素,并将指针加1

  public function next();

}

//具体的迭代器类

class ConcreteIterator implements IIterator

{

  //要迭代的集合

  public $collection;

  //指针

  public $index;

  //构造函数,确定迭代的集合,并将指针置零

  public function __construct($collection)

  {

    $this->collection = $collection;

    $this->index = 0;

  }

  //是否元素循环完毕

  public function hasNext()

  {

    if($this->index < $this->collection->getLength())

    {

        return true;

    }

    else

    {

        return false;

    }

  }

  //返回下一个元素,并将指针加1

  public function next()

  {

    $element = $this->collection->getAt($this->index);

    $this->index++;

    return $element;

  }

}

//初始化电脑的农民的聚集对象

$farmerAggregate = new ConcreteAggregate();

// 添加农民,这里简单的用字符串表示

$farmerAggregate->addElement(‘SVC1′);

$farmerAggregate->addElement(‘SVC2′);

// 获取迭代器

$iterator = $farmerAggregate->createIterator();

//将农民聚集对象循环

while ($iterator->hasNext())

{

  //获取下一个农民

  $element = $iterator->next();

  //我们简单的输出

  echo $element;

}

?>

用途总结:迭代器模式建立了类似数组的形式,从上面的代码可以看到,如果要修改循环的处理,或者修改被循环的集合,都不必修改其它相关的代码。

实现总结:需要一个管理聚集的类,比如上面的 ConcreteAggregate。另外需要迭代器类,比如上面的ConcreteIterator。然后把所有的操作,比如添加元素,获取下一个元素,指针之类的数组方面的操作抽象出来,这样其它的代码只要使用方法,比如getLength(),而不是细节化的count()函数,这样即使不用数组存放农民,也不需要改动聚集类以外的代码。

学习PHP设计模式13-原型模式

我们一般用new来新增对象,不过很多时候新增一个对象需要一些工作。而星际里面往往会新增某些类的大量的对象,比如新增很多机枪兵和龙骑。

待解决的问题:我们能否减少new的使用,同时避免需要新增对象的时候,了解对象的类名。

思路:php5提供了克隆方法,我们可以新增一个对象,然后每次需要新增和她同类的对象,克隆他就可以了。

原型(Prototype)模式示例:

<?php

//机枪兵类

class Marine

{

  //所属的用户ID

  public $playerID

  //构造函数,参数为用户的id

  public function __construct($id)

  {

  $this->playerID = $id;

  }

}

//兵种的管理类

class TroopManager

{

  // 数组,用于存放多个兵种的原型

  public $troopPrototype = array();

  //增加原型,第一个参数为原型的名字,第二个参数为原型对象

  public function addPrototype($name, $prototype)

  {

    $this->troopPrototype[$name] = $prototype;

  }

  // 获取原型的克隆,也就是替代new的方法,参数为原型的名字

  public function getPrototype($name)

  {

    return clone $this->troopPrototype[$name];

  }

}

// 初始化兵种的管理类

$manager = new TroopManager();

// 初始化两个属于不同玩家的机枪兵的原型

$m1 = new Marine(1);

$m2 = new Marine(2);

//增加原型,同时用比较容易记忆的名字来命名原型

$manager->addPrototype(‘Marine of 1′, $m1);

$manager->addPrototype(‘Marine of 2′, $m2);

//当需要新增对象的时候,我们可以不必知道对象的类名和初始化的工作

$m3 = $manager->getPrototype(‘Marine of 1′);

?>

用途总结:原型模式可以将新增对象的工作细节封装。

实现总结:需要一个原型管理类,实现增加和获取克隆原型的方法。注意这里由于为了简明,省略了一些东西,实际上我们可以在克隆方法上做一些改动,也可以用接口规范每个原型类。

学习PHP设计模式12-享元模式

星际的战斗达到后面,地图里面的部队很多,如果我们把每个兵的图像动画和属性值作为一个对象的话,系统的内存里会消耗极大。

我们在玩的时候会发现,因为星际里面的种族只有三个,其实兵种只有几十个。

虽然每个独立的士兵剩余的血不同,但是同一兵种的图像动画是一样的,即使不同的玩家,只是不同的颜色。比如每个人族的机枪兵。

而且大多数玩家只用到常用的一些兵种,很多时候不会制造所有的兵种。

待解决的问题:把把兵种的图像动画共享。

思路:我们把每个兵种的图像动画建模作为对象,放入内存共享。一旦有某个画面用到这个兵种,只要把共享的图像动画拿出来,更改颜色就可以了。

享元(Flyweight)模式示例:

<?php

//机枪兵享元

class MarineFlyweight

{

  //绘制机枪兵的图像动画,参数为状态,比如属于哪一个玩家

  public function drawMarine($state)

  {

  //绘制机枪兵

  }

}

// 享元工厂

class FlyweightFactory

{

  //享元数组,用于存放多个享元

  private $flyweights;

  //获取享元的方法

  public function getFlyweight($name)

  {

    if (!isset($flyweights[$name]))

    {

      $flyweights[$name] = new $name.”Flyweight”;

    }

    return $flyweights[$name];

  }

}

//初始化享元工厂

$flyweightFactory = new FlyweightFactory();

// 当我们需要绘制一个机枪兵的时候,同时传递一个状态数组,里面包含剩余的血等等

$marine = $flyweightFactory->getFlyweight(“Marine”);

$marine->drawMarine($status);

?>

用途总结:享元模式可以将需要共享的资源集中起来,统一管理,防止重复消耗。

实现总结:需要一个享元工厂管理共享的资源,比如上面的FlyweightFactory。把所有共享的资源的生产全部交给个享元工厂。

学习PHP设计模式11-建造器模式

星际里面有不少的任务关,也可以自己编辑地图,画面上有各种地形,建筑和部队。

这存在一个问题,初始化画面的流程很乱。

待解决的问题:完成初始化画面的工作,同时尽量减少各种绘制细节的耦合。

思路:既然星际的画面由几个部分组成:地图(就是地形和矿产),建筑,部队。那么我们把他们看成是零件,组装起来就是最后的产品(整个画面)。

建造器(Builder)模式示例:

<?php

// 规范制造各个零件的接口

interface Builder

{

  //制造地图零件

  public function buildMapPart();

  //制造建筑零件

  public function buildBuildingPart();

  // 制造部队零件

  public function buildArmyPart();

  // 组装零件

  public function getResult();

}

//实际建造器类,比如初始化某个任务关

class ConcreteBuilder implements Builder

{

  //制造地图零件

  public function buildMapPart()

  {

  //根据任务的设定画上地图

  echo ‘地图零件\n’;

  }

  // 制造建筑零件

  public function buildBuildingPart()

  {

  // 根据任务的设定画上建筑,包括玩家的和敌人的

  echo ‘建筑零件\n’;

  }

  //制造部队零件

  public function buildArmyPart()

  {

  //根据任务的设定画上部队,包括玩家的和敌人的

  echo ‘部队零件\n’;

  }

  // 组装零件

  public function getResult()

  {

  //将所有的东西叠加和处理,形成初始化画面

  echo ‘组装零件\n’;

  }

}

//监督类,也就是控制绘制流程的类

class Director

{

  // 私有属性,确定使用的建造器

  private $builder;

  // 构造方法,参数为选定的建造器对象

  public function __construct($builder)

  {

    //确定使用的建造器

    $this->builder = $builder;

  }

  //负责建造流程的方法,调用建造器对象的方法,制造所有零件

  public function buildeAllPart()

  {

    //制造地图零件

    $this->builder->buildMapPart();

    // 制造建筑零件

    $this->builder->buildBuildingPart();

    // 制造部队零件

    $this->builder->buildArmyPart();

  }

}

// 假设根据任务关,初始化我们需要的实际建造器对象

$concreteBuilder = new ConcreteBuilder();

//初始化一个监督对象

$director = new Director($concreteBuilder);

// 制造所有零件

$director->buildeAllPart();

// 最后让建造器组装零件,生成画面

$concreteBuilder->getResult();

?>

用途总结:建造器模式可以将流程和细节分离,各司其职。

实现总结:需要一个建造器接口或者抽象类,负责规范各个方法,比如上面的Builder。然后让实际的建造器类去实现所有的方法,比如上面的 ConcreteBuilder。同时需要负责流程管理的监督类,比如上面的Director,负责调用建造器的各个零件制造方法。最后让建造器去组装所有的零件。