OOP设计模式 - 行为型

Posted by 昆山吴彦祖 on 2021.12.28

行为型设计模式就更抽象了,大体上是通过一些特定的模式来解决特定的对象间互动问题。
其中比较常用的有:观察者模式、策略模式、模板方法模式
其他都属于适用于特定状况或者说用来解决特定场景的逻辑行为问题的
  1. 责任链模式
  2. 命令行模式
  3. 迭代器模式
  4. 中介者模式
  5. 备忘录模式
  6. 空对象模式
  7. 观察者模式
  8. 规格模式
  9. 状态模式
  10. 策略模式
  11. 模板方法模式
  12. 访问者模式

责任链模式

责任链模式将处理请求的对象连成一条链,沿着这条链传递该请求,直到有一个对象处理请求为止,这使得多个对象都有机会处理请求,从而避免请求的发送者和接受者之间的耦合关系。

案例:还是用游戏来做案例,魔兽世界中,你的每次攻击对敌方造成 碾压(你等级超过对方10级)| 未命中(当你的命中等级不够) | 暴击(本次命中了且触发暴击) | 普通伤害(命中未暴击) ... 等等

// 责任链的通用处理器类Handler(通常是一个接口或抽象类)
abstract handler(){
	private $next_handle = null;//处理者
	//追加责任链(处理者)
	public function append(handler $handler){
		$this->next_handle = $handler;
		return $handler;
	}
	//如果无结果,传递给下一个类处理
	public function handle(attack $attach,user $user){
		$attatk = $this->processiong(attack $attach,user $user);
		if(!$attatk)
				$attatk = $this->next_handle->handle(attack $attach,user $user);
		return $attatk;
	}

	abstract public processing(attack $attach,user $user);//真正的处理方法,需要在非抽象类中实现
}

//具体实现类
class nianya extends handle(){
	private $target_level;
	public function __construct($target_level){
		$this->target_level = $target_level;
	}
	public function processing(attack $attach,user $user){
		if($user->level - $this->target_level>=10){
			echo "触发碾压伤害!";
			$attach->hurt = $attach->jinengShanghai * $user->hurt * 10;//10倍伤害
			return $attach;
		}
		return false;
	}
}
class weimingzhong extends handle(){
	public function processing(attack $attach,user $user){
		if($user->mingzhong*100 < rand(1,100)){
			echo "运气真差,未命中!";
			$attach->hurt = 0;
			return $attach;
		}
		return false;
	}
}
class baoji extends handle(){
	public function processing(attack $attach,user $user){
		if($user->baoji*100 >= rand(1,100)){
			echo "暴击了!";
			$attach->hurt = $attach->jinengShanghai * $user->hurt * 1.5;
			return $attach;
		}
		return false;
	}
}
class mingzhong extends handle(){
	public function processing(attack $attach,user $user){
			$attach->hurt = $attach->jinengShanghai * $user->hurt;
			return $attach;
	}
}

// 应用
$attack = new attach([
	"level"=>"致死打击",
	"jinengShanghai"=>1.8,//技能伤害比例
]);
$user = new User([
	"level"=>100,
	"mingzhong"=>0.95, //命中率
	"baoji"=>0.24, //暴击率
	"hurt"=>760 //基础伤害值
]);
$tagget = new User([
	"level"=>95,
]);
$chain = new nianya($target->level); // 初始化一个责任链处理器
$chain
->append(new weimingzhong())
->append(new baoji())
->append(new mingzhong());// 附加其他处理器

$attack = $chain->handle($attack,$user);
echo $attack->hurt;

责任链模式 有点像是 foreach +break 循环遍历的优化。

像上面这个案例,我们平时处理的方法可能就是 把每一种伤害机制做一个独立类,然后外层封装一个类(里面通过循环遍历所有伤害机制,返回当前触发的伤害)

命令模式

命令模式和代理模式很像,就比如下面这个例子,你把服务员当成后厨、收银员的代理,完全也合乎情理。命令模式更为抽象难以理解

官方对于命令模式的解释:通过将命令单独抽离出系统,可以更好的实现 要对行为进行"记录、撤销/重做、事务"等处理

下面的是个人理解

优点:命令模式通过命令调用者 将命令的存储 和 执行分开,可以更好的做到命令的控制

  • 例如可以在fuwuyuan()→run()中进行每个菜品原材料的判断,如果某个菜品缺货,就停止执行所有菜品的制作;
  • 又比如可以在fuwuyuan()→run()中进行客户下单的数据记录。
  • 由于命令调用者中存储的命令中 包含了命令接收者(执行者)和命令所需参数。我们可以很方便的在fuwuyuan()→run()中丢给异步队列进行处理。貌似多进程task投递任务用的也是命令模式

缺点:命令模式比较适合命令接收者对外提供的方法较少(最好像案例中的厨师,只对客户提供点餐服务),不然我们就需要创造一堆的命令实现(因为每个命令实现只处理一种服务)

案例:饭店客人点餐,事实上我们可以直接找后厨。但是我们更希望招几个服务员,他们来处理客户点单,然后把点好的单转交给后厨。

这样厨师就只需要做菜(单一职责原则),不需要去应付客户点单。

//命令接口
interface commandInterface{
	public function execute();//命令的执行
	//public function undo();//命令的撤销
}
//命令实现
//客户点单命令
class cookFood implements commandInterface{
	public $receiver;
	public $food;
	public function __construct(receiver $receiver,food $food){
		$this->receiver= $receiver;
		$this->food = $food;
	}
	public function execute(){
		$this->receiver->do($this->food);
		// 在命令实现中可以很方便的实现所有命令的记录功能
		Log::info("***在***时间点了一份****");
	}
}
//客户买单命令
class maidan implements commandInterface{
	public $receiver;
	public $order ;
	public function __construct(receiver $receiver,order $order ){
		$this->receiver = $receiver;
		$this->order = $order ;
	}
	public function execute(){
		$this->receiver->check($this->order);
	}
}

//命令调用者
abstract invoker{
	public $commands = [];
	//增加命令
	public function setCommand(commandInterface $command){
		$this->commands[] = $command;
	}
	//移除命令
	public function removeCommand(commandInterface $command){
		foreach($this->commands as $k=>$command_){
			if($command_ == $command)
				unset($this->commands[$k]);
		}
	}
	//运行命令
	public function run(){
		echo "可以在命令的run方法中很方便的进行命令的批量控制";
		echo "也可以在这里很方便的投递队列任务,只需要把commands序列化投递即可,
		因为commands里包含了所有的异步任务需要的信息";
		foreach($this->commands as $k=>$command){
			$command->execute();
		}
		
		$this->commands = [];
	}
}
class fuwuyuan extends invoker{}

//命令接收者
interface receiver{
	public function do();
}

//各种厨子
abstract class chushi implements receiver{
	public $zhiwei;
	abstract public function do();
	public function daka(){
		echo "每天工作打卡";
	}
	public function kaihui(){
		echo "日常开会";
	}
}
class zhongcanchuzi implements receiver{
	public function __construct(){
		$this->zhiwei = "中餐厨子";
	}
	public function do(food $food){
		echo "我只会做中餐";
		if($food->method = "hongshao")
			$this->hongshao($food);
	}
	public function hongshao(){
	}
	public function youzha(){
	}
}
class tianpinchuzi implements receiver{
	public function __construct(){
		$this->zhiwei = "甜品厨子";
	}
	public function do(food $food){
		echo "我只会做甜品";
		$this->kao($food);
	}
	public function kao(){
	}
}
class jiushuichuzi implements receiver{
	public function __construct(){
		$this->zhiwei = "酒水厨子";
	}
	public function do(food $food){
		echo "我只会做酒水饮料";
	}
}

//各种文员
abstract class wenyuan implements receiver{
	public $zhiwei;
	abstract public function do();
	public function baobiao(){
		echo "文员需要每天做工作报表";
	}
	public function kaihui(){
		echo "日常开会";
	}
}
class shouyinyuan implements receiver{
	public function __construct(){
		$this->zhiwei = "收银员";
	}
	public function do(order $order){
		$order->price_total = $order->price_total - $coupon;
		echo "需要收款 $order->price_total";
	}
}

//应用
$this->invoker = new fuwuyuan();
$this->receiver['中餐'] = new zhongcanchuzi();
$this->receiver['甜品'] = new tianpinchuzi();
$this->receiver['酒水'] = new jiushuichuzi();
$this->receiver['收银'] = new shouyinyuan();
//命令调用者转达系统的命令 到命令接收者
//服务员(invoker消息调用者) 将客户(系统)的命令 传达给 后厨(消息接收者)
$food1 = new food("鱼香肉丝");
$command1 = new cookFood($this->receiver['中餐'],$food1);
$this->invoker->setCommand($command1);

$food2 = new food("红烧肉");
$command2 = new cookFood($this->receiver['中餐'],$food2);
$this->invoker->setCommand($command2);

$tianpin1 = new food("瑞士卷");
$command3 = new cookFood($this->receiver['甜品'],$tianpin1);
$this->invoker->setCommand($command3);

$this->invoker->removeCommand($command1);//突然不想点鱼香肉丝了
$this->invoker->run();

$this->invoker->setCommand(new cookFood($this->receiver['收银'],new order(8)));
$this->invoker->run();

迭代器模式

将对象转化为要给可以迭代的对象,php 的spl标准库中有一个迭代器接口Iterator,实现即可。

其实大部分情况下用数组就可以了,基本没有什么使用场景。

class game{
	public $name;
	public __construct($name){
		$this->name = $name;
	}
	
	public function play(){
		echo "$this->name 启动!";
	}
	public function stop(){
		echo "$this->name 关闭!";
	}
}
//让游戏集合实现一个迭代器
class gamelist implements \\Iterator,\\Countable {
	public $list = [];
	public $index = 0;
	public function add(game $game){
		$this->list[] = $game;
	}

	//下面几个是迭代器接口的方法
	public function current(){
		return $this->list[$index];
	}	
	public function next(){
		$this->index++;
	}	
	public function key(){
		return $this->index;
	}
	public function valid(){
		return isset($this->list[$this->index]);
	}
	public function rewind(game $game){
		$this->index = 0;
	}

	public function count(): int
  {
      return count($this->list);
  }
}

//应用
$games = new gamelist();
$games->add(new game("魔兽世界"));
$games->add(new game("魔兽争霸"));
$games->add(new game("星际争霸"));
while($games->valid()){
	$game = $games->current();
	$game->play();
	$games->next();
}

中介者模式

中介者模式使用一个中介对象来封装一系列 同事对象(同一级别或实现相同接口、抽象类的对象)的交互行为,他使各对象之间不再显式的引用,从而使其耦合松散,建立一个可扩展的应用架构。

在类关系上,中介者模式非常的像门面模式和代理模式。

区别:

服务对象上:

  • 中介者服务于一些列同事对象;
  • 门面提供子系统的接口封装,服务于外部系统;
  • 代理模式服务对象无限制,但大体上和门面差不多

类的功能上:

  • 中介者模式中 核心方法(服务于同事对象的方法)在中介者中实现(这也导致了当实例较多时,中介者类中方法较多,并且可能会经常变动 - 并不符合单一职责原则原则,存在众多因素可能导致中介者变化);
  • 门面类和代理类中的核心方法 在子系统、被代理的类中实现。

案例分析:

  • 大公司经常会有一些专门处理杂物的部门/人,他们做的事情就是各部门之间的沟通,这属于中介模式。
  • 相应的,总经理助理 需要为老板提供各部门的快捷服务,就属于门面模式。
  • 事实上,总经理助理 并不只是简单的提供各部门的服务,还需要对所有的服务进行整理和总结,这时候就属于代理模式。
// 这个不是必须的,只是为了明确杂物部属于中介者
interface mediator(){
	public function sendMessage();
}
// 中介者
class zawubu implements mediator{
	private $jishubu;
	private $caiwubu;
	private $shichangbu;
	private $caigoubu;
	public function __construct(){
		$this->jishubu= new jieshubu();
		$this->caiwubu= new caiwubu();
		$this->shichangbu= new shichangbu();
		$this->caigoubu= new caigoubu();
	}
	public function caigou($product,$number){
		$this->caigoubu->caigou($product,$number);//采购交给采购部
	}
	public function pay($user,$money){
		$this->caiwubu->pay($user,$money);//支付交给财务部
	}
	public function yanfa($doc){
		$this->jishubu->yanfa($doc);//研发就把文档交给技术部
	}
}

abstract bumen{
	public $people;
	public $boss;
	public $mediator;
	//初始化注入中介,或者后面注入都行
	public function __construct(mediator $mediator){
		$this->mediator = $mediator;
	}
}
class jishubu extends bumen{
	public function yanfa($doc){
		echo "打开文档,开始研发";
	}
	public function caigou($product,$number){
		$this->mediator->caigou($product,$number);//委托中介进行采购
	}
	public function pay($user,$money){
		$this->mediator->pay($user,$money);//委托中介支付
	}
}
class caiwubu extends bumen{
	public function pay($user,$money){
		echo "开始付钱";
	}
	public function caigou($product,$number){
		$this->mediator->caigou($product,$number);
	}
}
class caigoubu extends bumen{
	public function pay($user,$money){
		echo "开始付钱";
	}
	public function caigou($product,$number){
		echo "开始联系商家买东西";
	}
	public function pay($user,$money){
		$this->mediator->pay($user,$money);
	}
}
class shichangbu extends bumen{
	public function shichangdiaoyan($user,$money){
		echo "开始市场调研";
	}
	public function caigou($product,$number){
		$this->mediator->caigou($product,$number);
	}
	public function pay($user,$money){
		$this->mediator->pay($user,$money);
	}
	public function yanfa($doc){
		$this->mediator->yanfa($doc);//市场部把需求文档交给杂物部门委托研发
	}
}

//应用
$zawubu = new zawubu();
$jishubu = new jishubu($zawubu);
$jishubu->caigou("笔记本",10);
$jishubu->pay(100000);

备忘录模式

备忘录模式又叫做快照模式(Snapshot)或 Token 模式,备忘录模式的用意是在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样就可以在合适的时候将该对象恢复到原先保存的状态。

在系统需要对某些对象进行备份回档操作的时候,就可以用到备忘录模式。

案例:游戏中的存档与回档

//备忘录模式中的 发起人,负责创建备份
abstract class originator{
	abstract public function saveToMemento();
  abstract public function restoreFromMemento();
}
//备忘录 存储发起人的内部状态
class Memento{
	public $state;
	public function __construct($state){
		$this->state = $state;
	}
	public function getState(){
		return $this->state;
	}
}
class user extends originator(){
	public $level;
	public $prower;
	public $zhuangbei = [];
	
	public function saveToMemento(): Memento
  {
    return new Memento([clone $this->level,clone $this->prower,clone $this->zhuangbei]);
  }

  public function restoreFromMemento(Memento $memento)
  {
    $this->level= $memento->getState()[0];
    $this->prower= $memento->getState()[1];
    $this->zhuangbei= $memento->getState()[2];
  }
}
class guaiwu extends originator(){
	public $xueliang = 100;
	
	public function saveToMemento(): Memento
  {
    return new Memento(clone $this->xueliang );
  }

  public function restoreFromMemento(Memento $memento)
  {
    $this->xueliang = $memento->getState();
  }
}

//备忘录的管理员,用于统一管理、保存备忘录
class caretaker(){
	public $mements = [];
	// 备份
	public function saveToHistrory($key,Memento $memento){
		if(isset($this->mements[$key]))
			throw new \\Exception("备份已存在!");
		$this->mements[$key] = $memento;
	}
	// 回档
	public function getFromHistrory($key){
		return $this->mements[$key];
	}
}

//应用
echo "马上打怪了,备份下";
$user = new user();
$guaiwu = new guaiwu();
$caretaker = new caretaker();
$time = now();
$caretaker->saveToHistrory("user_".$time,$user->saveToMemento());
$caretaker->saveToHistrory("guaiwu_".$time,$guaiwu->saveToMemento());

echo "死掉了,回档!";
$user->restoreFromMemento($caretaker->getFromHistrory("user_".$time));
$guaiwu->restoreFromMemento($caretaker->getFromHistrory("guaiwu_".$time));

把对象中部分需要保存变化前后的数据的备份 独立存储到备忘录,由统一的备忘录管理器进行管理。在需要的时候可以通过管理器取出备忘录进行恢复。

是一个比较小众的设计模式,应用场景比较特定

空对象模式

简单来说就是对可能存在的空返回值进行优化,变成一个有统一方法的空对象。不需要调用端额外的判断

interface userInterface(){
	public function get();
}

class npc implements userInterface(){
	public function get(){
		$this->say();
	}
	public function say(){
		echo "你好,冒险者!";
	}
}

class player implements userInterface(){
	public function get(){
		echo "$user对$this->name说 $msg";
	}
	public function say(userInterface $user,$msg){
		$user->get(userInterface $user,$msg);
	}
}
class nobody implements userInterface(){
	public function get(userInterface $user,$msg){
		echo "$user说:$msg";
	}
}
$player->say($npc,"你好!");
$player->say(new nobody(),"你好!");

观察者模式

laravel中常见的模式之一,用于解耦对象与依赖对象变化的对象。

常见的就是laravel的模型观察者,当模型发生数据变化,可以通过模型的观察者操作相应的其他变化。

//被观察的对象
class player implements \\SplSubject{
	public $observers;
	public function __construct(){
	 $this->observers = new \\SplObjectStorage();
	}
	public function attach(SplObserver $observer){
		$this->observers->attach($observer);
	}
	public function detach(){
		$this->observers->$observer($observer);
	}
	public function notify(){
		foreach ($this->observers as $observer) {
	    $observer->update($this);
    }
	}
}

//观察者之一
class UserObserver implements \\SplObserver
{
    public function update(\\SplSubject $subject)
    {
        if($subject->level>100)
					mail($subject,"你100级了,赶紧来达拉然玩!");
    }

}

//应用
$player = new player();
$player->attach(new UserObserver());
$player->notify();

规格模式

组合模式的一种特定拓展,也是使用场景比较小众的一种设计模式。

通过把一系列有逻辑关系的条件筛选,层层转化为独立策略并且通过组合模式让所有独立策略可以相互组合使用。

让后期可能存在的新逻辑拓展变得更方便。

案例:产品下单后的 订单包邮验证 ,通过拆分可以转化为下面

  • 所有产品必须是有效售卖产品
    • 产品必须是有效售卖产品
    • 所有产品逻辑与判断
  • 订单总价必须大于10块 或者 订单中至少有1个包邮产品
    • 订单总价必须大于10块
    • 订单中至少有1个包邮产品
      • 产品必须是包邮产品
      • 所有产品逻辑或判断
//规格接口
interface specificationInterface{
	public function isSatisfiedBy():bool;
}

//3个固定的实现 and or not 用来实现对象之间的逻辑判断
class specificationAnd implements specificationInterface{
	private $items = [];
	public function __construct(...specificationInterface $specification){
		$this->items = $specification;
	}
	public function isSatisfiedBy(){
		foreach($this->items as $item){
			if(!$item->isSatisfiedBy()){
				return false;
			}
		}
		return true;
	}
}
class specificationOr implements specificationInterface{
	private $items = [];
	public function __construct(...specificationInterface $specification){
		$this->items = $specification;
	}
	public function isSatisfiedBy(){
		foreach($this->items as $item){
			if($item->isSatisfiedBy()){
				return true;
			}
		}
		return false;
	}
}
class specificationNot implements specificationInterface{
	private $item;
	public function __construct(specificationInterface $specification){
		$this->items = $specification;
	}
	public function isSatisfiedBy(){
		return !$item->isSatisfiedBy();
	}
}

//自定义实现
//判断产品总价是否达标
class specificationPriceTotal implements specificationInterface{
	private $items = [];
	public $price_total = 10;
	public function __construct(...specificationInterface $specification){
		$this->items = $specification;
	}
	public function isSatisfiedBy(){
		$price_total = 0;
		foreach($this->items as $item){
			$price_total = $item->$price
			if($price_total>$this->price_total)
				return true;
		}
		return false;
	}
}
//判断产品是否包邮
class specificationFreeShipping implements specificationInterface{
	private $item;
	public function __construct(specificationInterface $specification){
		$this->item = $specification;
	}
	public function isSatisfiedBy(){
		return $this->item->is_free_shipping();
	}
}

//产品类 | 兼职判断是否正常售卖产品
class product implements specificationInterface{
	private $product;
	public function isSatisfiedBy(){
		return $item->product && $product->shop->status && $product->shop->store;
	}
}

//应用
//判断所有产品必须有效
$specification1 = new specificationValidAnd($order->products);
//订单总价必须大于10块 或者 订单中至少有1个包邮产品
$specificationFreeShippings = [];
foreach($order->products as $product){
	$specificationFreeShippings[] = new specificationFreeShipping($product);
}
$specification2 = new specificationValidOr([
	new specificationPriceTotal($order->products),
	new specificationPriceOr($specificationFreeShippings);
]);
$specification = new specificationValidAnd($specification1,$specification2);
//最终验证
$specification->isSatisfiedBy();

状态模式

状态模式,通过将对象的状态变化逻辑 和 对象主题分离开,达到解耦的效果。

主要用于解决一些状态变化比较多的对象比如订单。

interface orderInterface(){
	public function done();//状态处理完成方法
}

class CreateOrder implements OrderInterface
{
    private $order;
    public function __construct(array $order)
    {
        if (empty($order)) {
            throw new \\Exception('Order can not be empty!');
        }
        $this->order = $order;
    }

    public function done()
    {
        $this->order['status'] = 'shipping';
        $this->order['updatedTime'] = time();

        return $this->updateOrder($this->order);
    }

}
class ShippingOrder implements OrderInterface
{
    private $order;

    public function __construct(array $order)
    {
        if (empty($order)) {
            throw new \\Exception('Order can not be empty!');
        }
        $this->order = $order;
    }

    public function done()
    {
        $this->order['status'] = 'completed';
        $this->order['updatedTime'] = time();

        // 将订单状态保存到数据库
        return $this->updateOrder($this->order);
    }
}

//应用
class OrderController{
	public function ship(){
		//这里的订单需要在控制器里通过传入的参数创建
		//可以用工厂模式代替多个独立的创建
		$order = new CreateOrder($order);
		$order->done();
	}
	public function complate(){
		$order = new ShippingOrder($order);
		$order->done();
	}
}

策略模式

将实例中变化的策略与实例分离开,让策略可以快速切换。

当我们代码中存在大量的if elseif... 就可以策略模式来优化了。大量的if else说明类本身涵盖的逻辑比较复杂,不符合单一职责原则原则(每个if情况下都存在不同变化),开闭原则。对于大型项目的后期拓展开发和维护是很不利的。

最常见的案例有产品的排序模式。

interface sortInterface(){
	public function sort();
}

class sortByCreatedtime implements sortInterface{
	public function sort(){
		return "created_at";
	}
}
class sortByHot implements sortInterface{
	public function sort(){
		return "is_hot";
	}
}
class sortByTuijian implements sortInterface{
	public function sort(){
		return "is_tuijian";
	}
}

class productReposiroty{
	public $sort;
	public getProducts(sortInterface $sort){
		//将不同模式下对应的检索方法通过注入而不是在内部ifelse判断
		$this->sort = $sort->sort();
		return $model->sort($sort)->get();
	}
}

模板方法模式

通过继承分离出共有方法和私有方法。最基础的设计模式之一

abstract class zhiye{
	//共有的方法
	final public function attack(){
		$jineng = $this->get_jineng();
		echo "开始使用$jineng 进攻";
	}
	abstarct public function get_jineng();
	abstarct public function zhuangjia();
}

class zhanshi extends zhiye{
	public function get_jineng(){
		return "致死打击";
	}
	public function zhuangjia(){
		$this->zhuangjia = "板甲";
	}
}
class fashi extends zhiye{
	public function get_jineng(){
		return "冰枪术";		
	}
	public function zhuangjia(){
		$this->zhuangjia = "布甲";
	}
}

//应用
$zhanshi = new zhanshi();
$zhanshi->attack();

访问者模式

据说是最难的一个设计模式

访问者模式是一种将数据操作和数据结构分离的设计模式。

这样说其实等于没说,太抽象了。我们直接来看一个案例

案例:魔兽世界的NPC 和 玩家直接的交互 大致分为3种

  • 提供服务 比如战士训练师 - 只对战士玩家提供训练服务
  • 任务流转 根据玩家的等级、职业、任务进度 提供任务完成、任务领取。
  • 默认对话

npc本身是一个比较固定的类,初始就设定好了 名称、提供的服务、默认的对话方式等等。

但是根据与npc交互的对象不同,比如游戏版本升级后,玩家中新增了死亡骑士角色。这时候某些npc面对死亡骑士玩家的交互时,会需要创建新的服务、任务 或者对话等等。

这是一个比较典型的适合用于访问者模式的功能设计,因为你不知道什么时候还会有新的玩家角色出来,意味着NPC提供的服务会不停增加、修改。

如果我们可以把面对不同玩家角色的交互 从NPC类的功能中分离出去(放到玩家类 或者专门的访问者类),是不是后期拓展性更好?NPC本身不需要再改动。这也更符合我们单一职责原则,NPC类仅仅负责定义这个NPC和他的核心属性(数据结构)。把交互(数据操作)分离出去独立完成。

这时候就可以用访问者模式了

简而言之:如果某些类本身数据和方法比较固定(npc、 银行柜台 等等),但是需要为不同的服务对象(玩家职业、 银行客户 等等)提供不同的数据操作,且服务对象后期有拓展可能,就可以考虑用访问者模式

//被访问者接口
interface interviewee{
	public function accept();//默认都需要实现 被访问
}
//npc基类 npc都是被访问者
class npc implements interviewee{	
	public $name;
	public $words;
	public function __construct($name,$words){
		$this->name = $name;
		$this->words = $words;
	}
	public function task(player $player){
		$tasks[] = task::get($player,$this);//获取当前玩家和当前npc间可交互的任务
	}
	public function talk(player $player){
		echo "$player->name,$this->words";
	}
	public function xunchi(player $player){
		echo "$player->name,你个杂种,你怎么敢来这里!";
	}
	public function accept(player $player){
		echo "提供访问接口,但是具体访问不在我这里执行,
		我把自己注入到访问者中,让访问者决定需要访问我什么内容";
		$player->visit($this);
	}
}
//守备官Npc
class shoubeiguan extends npc{	
	public function fight(){
		echo "发现入侵,开始攻击入侵对象!";
	}
}
//狮鹫管理员
class shijiuguanliyuan extends npc{
	public function showFlightPath(){
		echo "展示飞行路线图";
	}
	public function fly(){
		echo "骑上狮鹫,开始向目标点飞行";
	}
}
//职业训练师Npc
abstract class zhiyexunlianshi extends npc{
	abstract public $position;
	//职业训练师需要实现 职业技能训练
	abstract public function zhiyexunlian(player $player);
}

class zhanshixunlianshi extends zhiyexunlianshi{
	public $position = "战士训练师";
	public function xunlian(player $player){
		if ($player->zhiye != "战士")
			echo "你不是战士,不能为你提供训练服务!"
		echo "开始为战士提供职业训练";
	}
	//..其他私有方法
}
class fashixunlianshi extends zhiyexunlianshi{
	public $position = "法师训练师";
	public function zhiyexunlian(player $player){
		if ($player->zhiye != "法师")
			echo "你不是法师,不能为你提供训练服务!"
		echo "开始为法师提供职业训练";
	}
	//..其他私有方法
}
...
//专业技能训练师Npc
abstract class zhuanyelianshi extends npc{
	abstract public $position;
	//职业训练师需要实现 职业技能训练
	abstract public function zhuanyexunlian(player $player);
}

class zhanshixunlianshi extends zhiyexunlianshi{
	public $position = "采矿训练师";
	public function zhuanyexunlian(player $player){
		if (!in_array("采矿",$player->zhuanye))
			echo "你不会采矿";
		echo "开始提供采矿技能训练";
	}
	//..其他私有方法
}
class fumoxunlianshi extends zhiyexunlianshi{
	public $position = "附魔训练师";
	public function zhuanyexunlian(player $player){
		if (!in_array("附魔",$player->zhuanye))
			echo "你不会附魔"
		echo "开始提供附魔技能训练";
	}
	//..其他私有方法
}
...

//访问者接口
interface visitor{
	public function visit(interviewee $interviewee);
}

abstract class player{
	public $name;
	public $zhuanye = [];
	public $zhuanyexunlianshi_classes = [
		"采矿"=>"caikuangxunlianshi",
		"附魔"=>"fumoxunlianshi"
	];
	public __construct($name,$zhuanye){
		$this->name = $name;
		$this->zhuanye= $zhuanye;
	}
}
//具体访问者对象
//战士类
class zhanshi extends player implements visitor{
	public visit(interviewee $interviewee){
		$npc = $interviewee;
		$tasks = $npc->task($this);//判断当前npc是否有任务
		$talk = $npc->talk($this);//获取npc对话
		if($npc instanceof zhanshixunlianshi){
			$npc->zhiyexunlian($player);
		}
		foreach($this->zhuanye){
				if($npc instanceof $this->zhuanyexunlianshi_classes[$this->zhuanye]){
					$npc->zhuanyexunlian();
				}
		}
		if($npc instanceof shijiuguanliyuan){
			$npc->showFlightPath();
			$npc->fly($player);
		}
	}
}
//法师类
class fashi extends player implements visitor{
	public visit(interviewee $interviewee){
		$npc = $interviewee;
		$tasks = $npc->task($this);//判断当前npc是否有任务
		$talk = $npc->talk($this);//获取npc对话
		if($npc instanceof fashixunlianshi){
			$npc->xunlian($player);
		}
		foreach($this->zhuanye){
				if($npc instanceof $this->zhuanyexunlianshi_classes[$this->zhuanye]){
					$npc->zhuanyexunlian();
				}
		}
		if($npc instanceof shijiuguanliyuan){
			$npc->showFlightPath();
			$npc->fly($player);
		}
	}
}
//死亡骑士类
//死亡骑士因为剧情的关系,刚进入主城时候会遭到npc的训斥,而非普通对话
class dk extends player implements visitor{
	public visit(interviewee $interviewee){
		$npc = $interviewee;
		$tasks = $npc->task($this);//判断当前npc是否有任务
		$talk = $npc->xunchi($this);//被痛骂了一顿
		if($npc instanceof dkxunlianshi){
			$npc->xunlian($player);
		}
		foreach($this->zhuanye){
				if($npc instanceof $this->zhuanyexunlianshi_classes[$this->zhuanye]){
					$npc->zhuanyexunlian();
				}
		}
		if($npc instanceof shijiuguanliyuan){
			$npc->showFlightPath();
			$npc->fly($player);
		}
	}
}

//应用
$zhanshi1 = new zhanshi("张三",["采矿","附魔"]);
$npc1 = new zhanshixunlianshi("瓦里安.洛萨");
//玩家1开始访问战士训练师
$npc1->accept($zhanshi1);

$npc2 = new shijiuguanliyuan("死亡之翼");
//玩家1开始访问狮鹫管理员
$npc2->accept($zhanshi1);

$dk1 = new dk("巫妖王",["炼金"]);
$npc3 = new npc("路人甲");
//玩家3开始访问路人甲,但是被臭骂了一顿
$npc3->accept($dk1);

优势:

案例中,我们把所有npc(被访问者)和玩家(访问者)的交互行为放在 玩家类中,因为玩家类是会不停增加的,而npc是相对固定的类,当有新的玩家类出现后,可能所有npc都需要针对新玩家类提供新的交互。

这时候访问者模式的优势就体现出来了,我们只需要在新的玩家类中 实现和npc的新交互即可。

缺点:

所有访问者类中都依赖于具体的被访问者类,而不是依赖抽象。当被访问者类发送变化,可能要改动很多访问这类

设计模式