我的收藏

学习PHP设计模式10-代理模式

星际争霸如果是多人对战模式,就会遇到一个问题:如何降低网络延时和负担。

为了确保数据的一致性,我们应该将每个玩家的发生变化的数据不停的传送到开地图的主机进行保存,一旦任何某个玩家的客户机读取数据,就必须向主机请求数据。

尽管大多数数据是交互性的,即使某个玩家的人口也是这样的,如果某个敌人的部队杀死了这个玩家的一个部队,立即影响了他的人口数量。

不过水晶矿和气矿有所不同,除了玩家自己的建造操作和农民采集,别的玩家影响不了这个数据。

所以我们考虑在客户机也放一个数据存储,玩家改变或者读取他的资源的时候,先操作本机数据,再通知主机。

代理(Proxy)模式示例:

为了方便,假设客户机已经通过远程包含或其他方法获取了主机上的php代码,它的代码如下:

<?php

//客户机和主机操作数据时共同要实现的借口

interface iDataProcess

{

   //获取数据的方法,$ID表示玩家的ID,$dataName表示获取的数据的名称

  public function getData($ID, $dataName);

   //改变数据的方法,$ID表示玩家的ID,$dataName表示要改变的数据的名称,$dataValue表示改变后的数据的值

  public function updateData($ID, $dataName, $dataValue);

}

// 主机操作数据的类

class DataProcess implements iDataProcess

{

  // 获取数据的方法,$ID表示玩家的ID,$dataName表示获取的数据的名称

  public function getData($ID, $dataName)

  {

  // 操作数据库之类的代码

  }

  //改变数据的方法,$ID表示玩家的ID,$dataName表示要改变的数据的名称,$dataValue表示改变后的数据的值

  public function updateData($ID, $dataName, $dataValue)

  {

  //操作数据库之类的代码

  }

}

// 客户机操作数据的类,也就是代理类

class ProxyDataProcess implements iDataProcess

{

  // 主机操作数据的对象

  private $dataProcess;

  //构造函数

  public function __construct()

  {

    $this->dataProcess = new DataProcess();

  }

  // 获取数据的方法,$ID表示玩家的ID,$dataName表示获取的数据的名称

  public function getData($ID, $dataName)

  {

  //判断是否直接向主机请求

    switch ($dataName)

    {

    //如果查询水晶矿

      case ‘ore’:

    //直接从客户机保存的数据读取,详细代码略过

        break;

    //如果查询气矿

      case ‘gas’:

    //直接从客户机保存的数据读取,详细代码略过

        break;

      default:

        $this->dataProcess->getData($ID, $dataName);

    }

  }

  //改变数据的方法,$ID表示玩家的ID,$dataName表示要改变的数据的名称,$dataValue表示改变后的数据的值

  public function updateData($ID, $dataName, $dataValue)

  {

    //和读取的思路类似,如果是水晶矿或气矿,就先写入客户机的数据存储,再告诉主机修改

 }

}

//新建一个客户机处理数据的对象

$proxyDataProcess = new ProxyDataProcess();

// 假如显示本玩家自己的气矿剩余数量

$proxyDataProcess->getData(3, ‘gas’);

?>

用途总结:代理模式可以将让客户操作一个代理的类,进行一些降低资源消耗的工作,也可以完成比如权限验证的工作。
实现总结:需要一个接口来规定实际和代理操作类都必须实现的方法,比如上面iDataProcess,另外就是实际处理的类,比如上面DataProcess,以及让客户使用的代理操作类,比如上面的 ProxyDataProcess。其实代理模式可以有多种用法,这里限于篇幅,只讨论了降低数据操作的负荷。

学习PHP设计模式9-策略模式

星际开地图对战,等5秒钟进入地图后,每个玩家都会拥有一个基地,几个农民等,还会有初始的人口供给。但这些是根据种族的不同而不同。

待解决的问题:我们需要根据种族的不同,而对玩家进行不同的初始化,最好将这些不同的处理方式封装。

思路:定义初始化的接口,然后制作不同种族的初始化类。

策略模式(Strategy)示例:

为了使代码不至于过长,一部分类的定义不在此写出,如果要调试,请用字符串等方式替代new。

<?php

// 玩家的类

class player {

   //所属种族

  public $race;

  //部队

  public $army;

  //建筑

  public $building;

  //人口供给

  public $supply;

  //构造函数,设定所属种族

  public function __construct($race)

  {

  $this->race = $race; 

  }

}

//初始化的接口

interface initialPlayer {

   //制造初始化的部队

  public function giveArmy($player);

  //制造初始化的建筑

  public function giveBuilding($player);

  //初始化人口供给

  public function giveSupply($player);

}

//虫族的初始化算法

class zergInitial implements initialPlayer {

    // 制造初始化的部队

  public function giveArmy($player)

  {

  // 一个Overlord

  $player->army[] = new Overlord();

  //四个虫族农民

    for($i=0; $i<5;$i++)

    {

      $player->army[] = new Drone();

    }

  }

   // 制造初始化的建筑

  public function giveBuilding($player)

  {

   // 一个基地

   $player->building[] = new Hatchery();

  }

   //初始化人口供给

  public function giveSupply($player)

  {

   //虫族初始人口供给为9

   $player->supply = 9;

  }

}

// 人族的初始化算法

class terranInitial implements initialPlayer {

    //制造初始化的部队

  public function giveArmy($player)

  {

  //四个人族农民

    for($i=0; $i<5;$i++)

    {

      $player->army[] = new SVC();

    }

  }

   //制造初始化的建筑

  public function giveBuilding($player)

  {

   //一个基地

   $player->building[] = new Hatchery();

  }

   //初始化人口供给

  public function giveSupply($player)

  {

   //人族初始人口供给为10

   $player->supply = 10;

  }

}

// 初始化的控制类

class initialController {

   // 构造函数,参数为玩家的数组

  public function __construct($playerArray)

  {

    foreach ($playerArray as $player)

    {

        switch ($player->race)

        {

           case ‘zerg’:

           $initialController = new zergInitial();

           break;

           case ‘terran’:

           $initialController = new terranInitial();

           break;

        }

  $initialController->giveArmy($player);

  $initialController->giveBuilding($player);

  $initialController->giveSupply($player);

    }

  }

}

//假设两个虫族,一个人族

$playerArray = array(new player(‘zerg’), new player(‘zerg’), new player(‘terran’));

//进行初始化工作

$initialController = new initialController($playerArray);

?>

用途总结:策略模式可以将不同情况下的算法封装,根据具体的情况调用。

实现总结:需要一个接口,规定算法规范,使用者(比如初始化来)只要调用对应的算法就可以了。

学习PHP设计模式8-职责链模式

星际的兵种属性随着对平衡性的调节,会进行修改。如果这样的话,我们就要考虑减少一个事件和具体处理的关联性。

比如一颗原子弹投下的瞬间,在杀伤范围内的部队或者建筑都会减少血,但是随着距离中心点的远近,受损程度是不同的,而且不同的兵种和建筑受损情况是不同的。

待解决的问题:原子弹投下的瞬间,将杀伤的处理分别交给杀伤范围内的部队或者建筑自己的方法处理。

思路:建立一个接口,让所有的部队或者建筑实现。

职责链模式(Chain of Responsibility)示例:

<?php

// 被原子弹攻击的接口

interface NuclearAttacked {

    // 处理被原子弹攻击的方法,参数为投放点的x和y坐标

  public function NuclearAttacked($x, $y);

}

// 人族的基地,实现被原子弹攻击的接口,其他的内容暂时不考虑

class CommandCenter implements NuclearAttacked {

    //处理被原子弹攻击的方法,参数为投放点的x和y坐标

  public function NuclearAttacked($x, $y)

  {

    // 根据离原子弹中心的距离,定义减少的血,如果超出了剩余的血,就炸掉

  }

}

//巡洋舰(俗称大和),实现被原子弹攻击的接口,其他的内容暂时不考虑

class Battlecruiser implements NuclearAttacked {

    // 处理被原子弹攻击的方法,参数为投放点的x和y坐标

  public function NuclearAttacked($x, $y)

  {

    // 根据离原子弹中心的距离,定义减少的血,如果超出了剩余的血,就炸掉

  }

}

//原子弹类

class Nuclear {

//被原子弹攻击的对象

public $attackedThings;

    // 添加被原子弹攻击的对象

  public function addAttackedThings($thing)

  {

    // 添加被原子弹攻击的对象

    $this->attackedThings[] = $thing;

  }

    //原子弹爆炸的方法,参数为投放点的x和y坐标

  public function blast($x, $y)

  {

      // 把爆炸的事情交给所有涉及的对象,让他们自己处理

       foreach ($this->attackedThings as $thing)

       {

           // 把爆炸的事情交给所有涉及的对象,让他们自己处理

          $thing->NuclearAttacked($x, $y);

       }

  }

}

//新建一个基地对象

$CommandCenter = new CommandCenter();

//新建一个巡洋舰对象

$Battlecruiser = new Battlecruiser();

//造了一颗原子弹

$Nuclear2 = new Nuclear();

//假设投放成功,那个瞬间一个基地对象和一个巡洋舰对象在杀伤范围内

$Nuclear2->addAttackedThings($CommandCenter);

$Nuclear2->addAttackedThings($Battlecruiser);

// 原子弹爆炸,这样就把这个事件交给那些涉及的对象的处理方法,假设投放点的x和y坐标是2353, 368

$Nuclear2->blast(2353, 368);

?>

用途总结:职责链模式可以将一个涉及到多个对象的事件的处理交给对象自己处理,减少关联性。

实现总结:需要一个处理事件的接口,然后让所有的对象实现。

学习PHP设计模式7-观察者模式

最近很忙,写博客的时间比较少,所以写作进度比较慢。

当我们在星际中开地图和几家电脑作战的时候,电脑的几个玩家相当于结盟,一旦我们出兵进攻某一家电脑,其余的电脑会出兵救援。

那么如何让各家电脑知道自己的盟友被攻击了呢?并且自动做出反应?

待解决的问题:一旦某个电脑被我们进攻,其他电脑就获知,并且自动出兵救援。

思路:为电脑设置一些额外的观察系统,由他们去通知其他电脑。

观察者(Observer)模式示例:

<?php

//抽象的结盟类

abstract class abstractAlly {

// 放置观察者的集合,这里以简单的数组来直观演示

public $oberserverCollection;

    //增加观察者的方法,参数为观察者(也是玩家)的名称

  public function addOberserver($oberserverName)

  {

    以元素的方式将观察者对象放入观察者的集合

    $this->oberserverCollection[] = new oberserver($oberserverName);

  }

  //将被攻击的电脑的名字通知各个观察者

  public function notify($beAttackedPlayerName)

  {

        //把观察者的集合循环

        foreach ($this->oberserverCollection as $oberserver)

        {

        // 调用各个观察者的救援函数,参数为被攻击的电脑的名字,if用来排除被攻击的电脑的观察者

        if($oberserver->name != $beAttackedPlayerName) $oberserver->help($beAttackedPlayerName);

        }

  }

  abstract public function beAttacked($beAttackedPlayer);

}

// 具体的结盟类

class Ally extends abstractAlly {

    // 构造函数,将所有电脑玩家的名称的数组作为参数

  public function __construct($allPlayerName)

  {

        // 把所有电脑玩家的数组循环

        foreach ($allPlayerName as $playerName)

        {

        //增加观察者,参数为各个电脑玩家的名称

        $this->addOberserver($playerName);

        }

  }

  // 将被攻击的电脑的名字通知各个观察者

  public function beAttacked($beAttackedPlayerName)

  {

     // 调用各个观察者的救援函数,参数为被攻击的电脑的名字,if用来排除被攻击的电脑的观察者

     $this->notify($beAttackedPlayerName);

  }

}

// 观察者的接口

interface Ioberserver {

// 定义规范救援方法

function help($beAttackedPlayer);

}

//具体的观察者类

class oberserver implements Ioberserver {

//观察者(也是玩家)对象的名字

public $name;

    //构造函数,参数为观察者(也是玩家)的名称

  public function __construct($name)

  {

    $this->name = $name;

  }

  //观察者进行救援的方法

  public help($beAttackedPlayerName)

  {

        //这里简单的输出,谁去救谁,最后加一个换行,便于显示

        echo $this->name.” help “.$beAttackedPlayerName.”<br>”;

  }

  abstract public function beAttacked($beAttackedPlayer);

}

//假设我一对三,两家虫族,一家神族

$allComputePlayer = array(‘Zerg1′, ‘Protoss2′, ‘Zerg2′);

//新建电脑结盟

$Ally = new Ally($allComputePlayer);

//假设我进攻了第二个虫族

$Ally->beAttacked(‘Zerg2′);

?>

用途总结:观察者模式可以将某个状态的变化立即通知所有相关的对象,并调用对方的处理方法。

实现总结:需要一个观察者类来处理变化,被观察的对象需要实现通知所有观察者的方法。

学习PHP设计模式6-正面模式

星际里面的战斗都是在地图上进行的,只要我们可以编辑地图,就可以创造一些新的战役。可是,星际里面的地图绘制相关的代码如果开放出来,估计大多数万家都看不懂,更不要说自己编辑地图了。

待解决的问题:在不了解地图代码的结构下,我们要让玩家自己编辑地图。

思路:对于玩家而言,他熟悉的是水晶矿,高地这些形状,他和系统通过鼠标交互。我们可以设计一个地图编辑器让玩家使用,而无需让他研究绘制地图的细节代码。(实际上暴雪公司就是这样做的,很多玩家甚至暴雪内部人员都是用星际中的地图编辑器制作地图)

正面模式(Facade)示例:

<?php

//玩家的鼠标对象,记录鼠标在编辑其中的状态

class mouse {

//鼠标所处的X轴坐标

public static $X;

//鼠标当前能绘制的对象,比如水晶矿,河流等

public static $object;

//鼠标所处的Y轴坐标

public static $Y;

}

//地图编辑器

class mapEdit {

    //绘制方法

  public static function draw()

  {

    //根据鼠标对象的状态在地图上绘制各种东西

    //如果是水晶矿

    if(mouse::$object == “ore”)

    {

    //调用水晶矿类的绘制方法,这个类定义在下面,这是真正的绘制,但玩家不必学习他的细节

    ore::draw();

    //如果是河流

    }elseif(mouse::$object == “river”){

    // 调用河流类的绘制方法,这个类定义在下面,这是真正的绘制,但玩家不必学习他的细节

    river::draw();

    }

  }

}

//水晶矿类

classore{

    // 剩余的矿,以及其他属性,这里略过

  public $remain;

    // 绘制水晶矿

  public static function draw()

  {

   //实际的绘制水晶矿的底层细节代码

  }

}

// 河流类

class river {

    //绘制河流

  public static function draw()

  {

   //实际的绘制河流的底层细节代码

  }

}

// 玩家在地图编辑器上点击绘制对象列表上的水晶矿对象

mouse::$object = “ore”;

//玩家移动鼠标

mouse::$X = 311;

mouse::$Y = 126;

// 在地图上点击,表示绘制当前对象,也就是一个水晶矿

mapEdit::draw();

?>

用途总结:正面模式让使用者集中于他所要进行的工作,而不必知道全部细节,或者说提供了一个容易使用的工具,同时屏蔽了底层细节,不必让使用者重新学习。

实现总结:需要一个类似上面地图编辑器的代码类,帮助玩家方便的进行操作。