大话 PHP 设计模式--行为型
前言
我们学习的设计模式分为三类:创建者模式、结构型模式、行为型模式;创建型模式与对象的创建有关;结构型模式处理类或对象的组合;而行为型模式是对类或对象怎样交互和怎样分配职责进行描述;
内容:上一篇介绍PHP 设计模式的结构型一篇,这一篇是行为型。包括:责任链模式(Chain Of Responsibilities),命令行模式(Command),解释器模式(Interpreter),迭代器模式(Iterator),中介者模式(Mediator),备忘录模式(Memento),观察者模式(Observer),状态模式(State),策略模式(Strategy),模板方法模式(Template Method),访问者模式(Visitor),空对象模式(Null Object),规格模式(Specification)
(一)责任链模式(Chain Of Responsibilities)
- 定义
建立一个对象链来按指定顺序处理调用。如果其中一个对象无法处理命令,它会委托这个调用给它的下一个对象来进行处理,以此类推。
- 解释例: 晚上去上英语课,为了好开溜坐到了最后一排,哇,前面坐了好几个漂亮的MM哎,找张纸条,写上"Hi,可以做我的女朋友吗?如果不愿意请向前传",纸条就一个接一个的传上去了,糟糕,传到第一排的MM把纸条传给老师了,听说是个老处女呀,快跑!
- 实例: 查看指定接口数据是否存在,如果不存在,读取缓存数据.本例只实现两层链
-
实例代码实现
abstract class Handler { private $successor = null; public function __construct(Handler $handler = null) { $this->successor = $handler; } final public function handle() { $processed = $this->processing(); if ($processed === null) { // 请求尚未被目前的处理器处理 => 传递到下一个处理器。 if ($this->successor !== null) { $processed = $this->successor->handle(); } } return $processed; } abstract protected function processing(); } class HttpInMemoryCacheHandler extends Handler { private $data; public function __construct(array $data, Handler $successor = null) { parent::__construct($successor); $this->data = $data; } protected function processing() { $key = sprintf( '%s?%s', $_SERVER['SERVER_NAME'], $_SERVER['SCRIPT_NAME'] ); if ($_SERVER['REQUEST_METHOD'] == 'GET' && isset($this->data[$key])) { return $this->data[$key]; } return null; } } class SlowDatabaseHandler extends Handler { protected function processing() { return 'Hello World!'; } } $chain = new HttpInMemoryCacheHandler(['localhost?/index.php' => 'Hello In Memory!'],new SlowDatabaseHandler()); echo $chain->handle();
(二)命令行模式(Command)
- 定义
命令模式把一个请求或者操作封装到一个对象中。命令模式把发出命令的责任和执行命令的责任分割开,委派给不同的对象。命令模式允许请求的一方和发送的一方独立开来,使得请求的一方不必知道接收请求的一方的接口,更不必知道请求是怎么被接收,以及操作是否执行,何时被执行以及是怎么被执行的。系统支持命令的撤消。分为 命令角色-具体命令角色-客户角色-请求者角色-接收者角色
- 解释例: 俺有一个MM家里管得特别严,没法见面,只好借助于她弟弟在我们俩之间传送信息,她对我有什么指示,就写一张纸条让她弟弟带给我。这不,她弟弟又传送过来一个COMMAND,为了感谢他,我请他吃了碗杂酱面,哪知道他说:"我同时给我姐姐三个男朋友送COMMAND,就数你最小气,才请我吃面。"
- 实例: 一个简单的增加和粘贴功能为例,将这两个命令组成一个宏命令。以新建复制命令和粘贴命令,
然后将其添加到宏命令中去。 -
实例代码实现
interface Command { public function execute(); } interface MacroCommand extends Command { /** * 宏命令聚集的管理方法,可以删除一个成员命令 * @param Command $command */ public function remove(Command $command); /** * 宏命令聚集的管理方法,可以增加一个成员命令 * @param Command $command */ public function add(Command $command); } class DemoMacroCommand implements MacroCommand { private $_commandList; public function __construct() { $this->_commandList = array(); } public function remove(Command $command) { $key = array_search($command, $this->_commandList); if ($key === FALSE) { return FALSE; } unset($this->_commandList[$key]); return TRUE; } public function add(Command $command) { return array_push($this->_commandList, $command); } public function execute() { foreach ($this->_commandList as $command) { $command->execute(); } } } /** * 具体拷贝命令角色 */ class CopyCommand implements Command { private $_receiver; public function __construct(Receiver $receiver) { $this->_receiver = $receiver; } /** * 执行方法 */ public function execute() { $this->_receiver->copy(); } } /** * 具体拷贝命令角色 */ class PasteCommand implements Command { private $_receiver; public function __construct(Receiver $receiver) { $this->_receiver = $receiver; } /** * 执行方法 */ public function execute() { $this->_receiver->paste(); } } /** * 接收者角色 */ class Receiver { /* 接收者名称 */ private $_name; public function __construct($name) { $this->_name = $name; } /** * 复制方法 */ public function copy() { echo $this->_name, ' do copy action.<br />'; } /** * 粘贴方法 */ public function paste() { echo $this->_name, ' do paste action.<br />'; } } /** * 请求者角色 */ class Invoker { private $_command; public function __construct(Command $command) { $this->_command = $command; } public function action() { $this->_command->execute(); } } /** * 客户端 */ class Client { /** * Main program. */ public static function main() { $receiver = new Receiver('phpppan'); $pasteCommand = new PasteCommand($receiver); $copyCommand = new CopyCommand($receiver); $macroCommand = new DemoMacroCommand(); $macroCommand->add($copyCommand); $macroCommand->add($pasteCommand); $invoker = new Invoker($macroCommand); $invoker->action(); $macroCommand->remove($copyCommand); $invoker = new Invoker($macroCommand); $invoker->action(); } } Client::main();
(三)解释器模式(Interpreter)
- 定义
给定一个语言, 定义它的文法的一种表示,并定义一个解释器,该解释器使用该表示来解释语言中的句子。角色: 1)环境角色:定义解释规则的全局信息。2)抽象解释器:定义了部分解释具体实现,封装了一些由具体解释器实现的接口。3)具体解释器(MusicNote)实现抽象解释器的接口,进行具体的解释执行。
- 解释例: 俺有一个《泡MM真经》,上面有各种泡MM的攻略,比如说去吃西餐的步骤、去看电影的方法等等,跟MM约会时,只要做一个Interpreter,照着上面的脚本执行就可以了。
- 实例: 中英文解释
- 实例代码实现
//环境角色,定义要解释的全局内容 class Expression{ public $content; function getContent(){ return $this->content; } } //抽象解释器 abstract class AbstractInterpreter{ abstract function interpret($content); } //具体解释器,实现抽象解释器的抽象方法 class ChineseInterpreter extends AbstractInterpreter{ function interpret($content){ for($i=1;$i<count($content);$i++){ switch($content[$i]){ case '0': echo "没有人<br>";break; case "1": echo "一个人<br>";break; case "2": echo "二个人<br>";break; case "3": echo "三个人<br>";break; case "4": echo "四个人<br>";break; case "5": echo "五个人<br>";break; case "6": echo "六个人<br>";break; case "7": echo "七个人<br>";break; case "8": echo "八个人<br>";break; case "9": echo "九个人<br>";break; default:echo "其他"; } } } } class EnglishInterpreter extends AbstractInterpreter{ function interpret($content){ for($i=1;$i<count($content);$i++){ switch($content[$i]){ case '0': echo "This is nobody<br>";break; case "1": echo "This is one people<br>";break; case "2": echo "This is two people<br>";break; case "3": echo "This is three people<br>";break; case "4": echo "This is four people<br>";break; case "5": echo "This is five people<br>";break; case "6": echo "This is six people<br>";break; case "7": echo "This is seven people<br>";break; case "8": echo "This is eight people<br>";break; case "9": echo "This is nine people<br>";break; default:echo "others"; } } } } //封装好的对具体解释器的调用类,非解释器模式必须的角色 class Interpreter{ private $interpreter; private $content; function __construct($expression){ $this->content = $expression->getContent(); if($this->content[0] == "Chinese"){ $this->interpreter = new ChineseInterpreter(); }else{ $this->interpreter = new EnglishInterpreter(); } } function execute(){ $this->interpreter->interpret($this->content); } } //中文解释 $expression = new Expression(); $expression->content = array("Chinese",3,2,4,4,5); $interpreter = new Interpreter($expression); $interpreter->execute(); //英文解释 $expression = new Expression(); $expression->content = array("English",1,2,3,0,0); $interpreter = new Interpreter($expression); $interpreter->execute();
(四)迭代器模式 (Iterator)
- 定义
迭代器模式 (Iterator),又叫做游标(Cursor)模式。提供一种方法访问一个容器(Container)对象中各个元素,而又不需暴露该对象的内部细节。
- 解释例: 当你需要访问一个聚合对象,而且不管这些对象是什么都需要遍历的时候,就应该考虑使用迭代器模式。另外,当需要对聚集有多种方式遍历时,可以考虑去使用迭代器模式。迭代器模式为遍历不同的聚集结构提供如开始、下一个、是否结束、当前哪一项等统一的接口。
-
实例代码实现
class sample implements Iterator { private $_items ; public function __construct(&$data) { $this->_items = $data; } public function current() { return current($this->_items); } public function next() { next($this->_items); } public function key() { return key($this->_items); } public function rewind() { reset($this->_items); } public function valid() { return ($this->current() !== FALSE); } // client $data = array(1, 2, 3, 4, 5); $sa = new sample($data); foreach ($sa as $key => $row) { echo $key, ' ', $row, '<br />'; } }
(五)中介者模式(Mediator)
- 定义
中介者模式包装了一系列对象相互作用的方式,使得这些对象不必相互明显作用。从而使他们可以松散偶合。当某些对象之间的作用发生改变时,不会立即影响其他的一些对象之间的作用。保证这些作用可以彼此独立的变化。中介者模式将多对多的相互作用转化为一对多的相互作用。中介者模式将对象的行为和协作抽象化,把对象在小尺度的行为上与其他对象的相互作用分开处理。两大角色: 1)中介者角色 2)具体对象角色
- 解释例: *四个MM打麻将,相互之间谁应该给谁多少钱算不清楚了,幸亏当时我在旁边,按照各自的筹码数算钱,赚了钱的从我这里拿,赔了钱的也付给我,一切就OK啦,俺得到了四个MM的电话。**
- 实例: 模拟客户端请求数据的过程.
-
实例代码实现
//中介者角色接口 interface MediatorInterface { //发出响应 public function sendResponse($content); //做出请求 public function makeRequest(); //查询数据库 public function queryDb(); } //中介者角色 class Mediator implements MediatorInterface { /** * @var Server */ private $server; /** * @var Database */ private $database; /** * @var Client */ private $client; /** * @param Database $database * @param Client $client * @param Server $server */ public function __construct(Database $database, Client $client, Server $server) { $this->database = $database; $this->server = $server; $this->client = $client; $this->database->setMediator($this); $this->server->setMediator($this); $this->client->setMediator($this); } public function makeRequest() { $this->server->process(); } public function queryDb(): string { return $this->database->getData(); } /** * @param string $content */ public function sendResponse($content) { $this->client->output($content); } } //抽象对象角色 abstract class Colleague { /** * 确保子类不变化。 * * @var MediatorInterface */ protected $mediator; /** * @param MediatorInterface $mediator */ public function setMediator(MediatorInterface $mediator) { $this->mediator = $mediator; } } //客户端对象 class Client extends Colleague { public function request() { $this->mediator->makeRequest(); } public function output(string $content) { echo $content; } } //数据据对象 class Database extends Colleague { public function getData(): string { return 'World'; } } //服务器对象 class Server extends Colleague { public function process() { $data = $this->mediator->queryDb(); $this->mediator->sendResponse(sprintf("Hello %s", $data)); } } $client = new Client(); new Mediator(new Database(), $client, new Server()); $client->request();
(六)备忘录模式(Memento)
- 定义
备忘录对象是一个用来存储另外一个对象内部状态的快照的对象。备忘录模式的用意是在不破坏封装的条件下,将一个对象的状态捉住,并外部化,存储起来,从而可以在将来合适的时候把这个对象还原到存储起来的状态。
1.Originator(发起人):负责创建一个备忘录Memento,用以记录当前时刻自身的内部状态,并可使用备忘录恢复内部状态。Originator可以根据需要决定Memento存储自己的哪些内部状态。
2.Memento(备忘录):负责存储Originator对象的内部状态,并可以防止Originator以外的其他对象访问备忘录。备忘录有两个接口:Caretaker只能看到备忘录的窄接口,他只能将备忘录传递给其他对象。Originator却可看到备忘录的宽接口,允许它访问返回到先前状态所需要的所有数据。
3.Caretaker(管理者):负责备忘录Memento,不能对Memento的内容进行访问或者操作。 - 解释例: 同时跟几个MM聊天时,一定要记清楚刚才跟MM说了些什么话,不然MM发现了会不高兴的哦,幸亏我有个备忘录,刚才与哪个MM说了什么话我都拷贝一份放到备忘录里面保存,这样可以随时察看以前的记录啦。
- 实例: 设计一个具有记忆历史状态的编辑器
-
实例代码实现
//编辑器 class Edit { public $act; public $content; //保存状态 public function saveState(Caretaker $caretaker) { return $caretaker->add(new Memento($this->act,$this->content)); } //恢复状态 public function recoveryState(Memento $memento) { $this->act = $memento->act; $this->content = $memento->content; } public function modify($act,$content) { $this->act = $act; $this->content = $content; } public function show() { echo "操作:".$this->act."\r\n"; echo "内容:".$this->content."\r\n"; } } //编辑状态存储箱 class Memento { public $act; public $content; public function __construct($act,$content) { $this->act = $act; $this->content = $content; } } //管理类 class Caretaker { public $data = array(); public $pop = array(); public function add(Memento $memento) { $this->data[] = $memento; } //获取上一个 public function getLast() { $len = count($this->data); if($len < 1) { return; } $lastData = $this->data[$len - 1]; $this->pop[] = array_pop($this->data); return $lastData; } //获取下一个 public function getNext() { $len = count($this->pop); if($len < 1) { return; } $nextData = $this->pop[$len - 1]; $this->data[] = array_pop($this->pop); return $nextData; } } class Client { public static function main() { $edit = new Edit(); $caretaker = new Caretaker(); $edit->modify('插入abc','abc'); $edit->saveState($caretaker); $edit->modify('插入d','abcd'); $edit->saveState($caretaker); $edit->modify('删除c','abd'); $edit->saveState($caretaker); $edit->modify('插入e','abde'); //恢复上一个状态 $edit->recoveryState($caretaker->getLast()); //删除c $edit->recoveryState($caretaker->getLast()); //插入d $edit->recoveryState($caretaker->getLast()); //插入abc //恢复下一个状态 $edit->recoveryState($caretaker->getNext()); //插入abc $edit->recoveryState($caretaker->getNext()); //插入d $edit->recoveryState($caretaker->getNext()); //删除c $edit->show(); } } Client::main();
(七)代理模式(Proxy)
- 定义
代理模式(Proxy)为其他对象提供一种代理以控制对这个对象的访问。使用代理模式创建代理对象,让代理对象控制目标对象的访问(目标对象可以是远程的对象、创建开销大的对象或需要安全控制的对象),并且可以在不改变目标对象的情况下添加一些额外的功能.
- 解释例: 在某些情况下,一个客户不想或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用,并且可以通过代理对象去掉客户不能看到的内容和服务或者添加客户需要的额外服务。
- 实例: 代理主题角色.
-
实例代码实现
abstract class Subject { // 抽象主题角色 abstract public function action(); } class RealSubject extends Subject { // 真实主题角色 public function __construct() {} public function action() {} } class ProxySubject extends Subject { // 代理主题角色 private $_real_subject = NULL; public function __construct() {} public function action() { $this->_beforeAction(); if (is_null($this->_real_subject)) { $this->_real_subject = new RealSubject(); } $this->_real_subject->action(); $this->_afterAction(); } private function _beforeAction() { echo '在action前,我想干点啥....'; } private function _afterAction() { echo '在action后,我还想干点啥....'; } } // client $subject = new ProxySubject(); $subject->action();//输出:在action前,我想干点啥....在action后,我还想干点啥....
(七)观察者模式(Observer)
- 定义
观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态上发生变化时,会通知所有观察者对象,使他们能够自动更新自己。
- 解释例: 想知道咱们公司最新MM情报吗?加入公司的MM情报邮件组就行了,tom负责搜集情报,他发现的新情报不用一个一个通知我们,直接发布给邮件组,我们作为订阅者(观察者)就可以及时收到情报啦
- 实例: 通过php内置的Spl标准类实现观察者.其中观察者(obServer)由三个元素组成:SplSubject,SplObjectStrorage,SplObserver,SplSubject具体操作者,SplObjectStorage用于处理存储对象和删除对象,SplObserver接收通知的观察者.本例实现一个购票系统,登录成功之后依次记录日志和发送折扣劵通知.
- 实例代码实现
class buyTicket implements SplSubject { private $ticket; private $storage; function __construct(){ $this->storage = new SplObjectStorage(); } function attach( SplObserver $object ){ $this->storage->attach($object); } function detach( SplObserver $object ){ $this->storage->detach($object); } function notify(){ foreach ( $this->storage as $obs ){ $obs->update( $this ); } } //买票成功,发送通知记录日志,送折扣劵 function buyTicket(){ $this->ticket = 'NO.12, $100.'; $this->notify(); } function getTicket(){ return $this->ticket; } } abstract class ticketObject implements SplObserver { /** * 接收通知的观察者 * 判断通知来源, 并且相应观察者执行相应的操作 */ private $buyTicket; function __construct( buyTicket $buyTicket ){ $this->buyTicket = $buyTicket; $buyTicket->attach( $this ); } function update( SplSubject $subject ) { if( $subject === $this->buyTicket ){ $this->doUpdate( $subject ); } } abstract function doUpdate( SplSubject $buyTicket ); } //日志观察者接到通知记录日志 class ticketLogger extends ticketObject { function doUpdate( SplSubject $buyTicket ){ print '日志计入 买票 '.$buyTicket->getTicket()."\n"; } } //折扣劵观察者 接到通知发送折扣劵 class ticketDid extends ticketObject { function doUpdate( SplSubject $buyTicket ){ print "送10元折扣卷一张".$buyTicket->getTicket(); } } $ticket = new buyTicket(); new ticketLogger( $ticket ); new ticketDid( $ticket ); $ticket->buyTicket();
(八)状态模式(State)
- 定义
准备一个抽象类,将部分逻辑以具体方法以及具体构造子的形式实现,然后声明一些抽象方法来迫使子类实现剩余的逻辑。不同的子类可以以不同的方式实现这些抽象方法,从而对剩余的逻辑有不同的实现。先制定一个顶级逻辑框架,而将逻辑的细节留给具体的子类去实现。
- 使用场景: 1)如果项目中存在太多的 if {...} elseif {...} else {...} 。那么你应该考虑状态模式 2) 如果每个状态中处理的业务逻辑特别复杂 3)如果代码中的状态相对固定。比如一个电商中购买商品的流程:未支付、已过期、待发货(已支付)、已发货、已收货。那么这种状态基本上定下来不会有太大变化,状态发生在内部中,顺序固定,不需要客户端进行处理。
- 实例: 模拟一个条件判断的流程.
-
实例代码实现
interface State { // 注意这里的Context 我在后面会讲到。不等同于上面的 Context 类哦 public function handle(Context $context); } // 状态A class StateA implements State { public function handle(Context $context) { if ($context->term == 1) { // 处理逻辑,并终止程序 echo "11111"; } else { $context->setState(new StateB()); $context->request(); } } } // 状态B class StateB implements State { public function handle(Context $context) { if ($context->term == 2) { // 处理逻辑,并终止程序 echo "2222"; } else { $context->setState(new StateC()); $context->request(); } } } // 状态C class StateC implements State { public function handle(Context $context) { // 如果还有其他状态,则继续往下走。如果没有,就在次终止程序 if ($context->term == 3) { // 处理逻辑,并终止程序 echo "3333"; } else { echo "4444"; } } } //状态管理器(上下文环境) class Context { private $state;// 用来保存 State 对象 public $term; public function setState(State $state) { $this->state = $state; } public function request() { $this->state->handle($this); } } $context = new Context; $context->term = 2; $context->setState(new StateA()); $context->request();
(九)策略模式(Strategy)
- 定义
策略模式针对一组算法,将每一个算法封装到具有共同接口的独立的类中,从而使得它们可以相互替换。策略模式使得算法可以在不影响到客户端的情况下发生变化。策略模式把行为和环境分开。环境类负责维持和查询行为类,各种算法在具体的策略类中提供。由于算法和环境独立开来,算法的增减,修改都不会影响到环境和客户端。
- 解释例: 跟不同类型的MM约会,要用不同的策略,有的请电影比较好,有的则去吃小吃效果不错,有的去海边浪漫最合适,单目的都是为了得到MM的芳心,我的追MM锦囊中有好多Strategy哦。
- 实例: 对二维数组分别进行id和date排序.
-
实例代码实现
class Context { private $comparator; public function __construct(ComparatorInterface $comparator) { $this->comparator = $comparator; } public function executeStrategy(array $elements) : array { uasort($elements, [$this->comparator, 'compare']); return $elements; } } interface ComparatorInterface { public function compare($a, $b): int; } class DateComparator implements ComparatorInterface { public function compare($a, $b): int { $aDate = new \DateTime($a['date']); $bDate = new \DateTime($b['date']); return $aDate <=> $bDate; } } class IdComparator implements ComparatorInterface { public function compare($a, $b): int { return $a['id'] <=> $b['id']; } } $collection = array( 0 => array( 'id' => 4, 'date' => '2019/9/5 15:8:15' ), 1 => array( 'id' => 8, 'date' => '2019/9/5 12:8:15' ), 2 => array( 'id' => -1, 'date' => '2019/9/5 10:8:15' ), 3 => array( 'id' => -9, 'date' => '2019/9/5 11:8:15' ), ); $obj = new Context(new IdComparator()); $elements = $obj->executeStrategy($collection); $firstElement = array_shift($elements); $obj = new Context(new DateComparator()); $elements = $obj->executeStrategy($collection); $firstElement = array_shift($elements); // var_dump($firstElement);
(十)模板方法模式(Template Method)
- 定义
定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。 抽象模板-具体模板
- 实例: 一个旅行模板.
-
实例代码实现
abstract class Journey { private $thingsToDo = []; /** * 这是当前类及其子类提供的公共服务 * 注意,它「冻结」了全局的算法行为 * 如果你想重写这个契约,只需要实现一个包含 takeATrip() 方法的接口 */ final public function takeATrip() { $this->thingsToDo[] = $this->enjoyVacation(); $buyGift = $this->buyGift(); if ($buyGift !== null) { $this->thingsToDo[] = $buyGift; } } /** * 这个方法必须要实现,它是这个模式的关键点 */ abstract protected function enjoyVacation(): string; /** * 这个方法是可选的,也可能作为算法的一部分 * 如果需要的话你可以重写它 * * @return null|string */ protected function buyGift() { return null; } /** * @return string[] */ public function getThingsToDo(): array { return $this->thingsToDo; } } class BeachJourney extends Journey { protected function enjoyVacation(): string { return "Swimming and sun-bathing"; } } class CityJourney extends Journey { protected function enjoyVacation(): string { return "Eat, drink, take photos and sleep"; } protected function buyGift(): string { return "Buy a gift"; } } $beachJourney = new BeachJourney(); $beachJourney->takeATrip(); $beachJourney->getThingsToDo(); $cityJourney = new CityJourney(); $cityJourney->takeATrip(); $cityJourney->getThingsToDo();
(十一)访问者模式(Visitor)
- 定义
访问者模式的目的是封装一些施加于某种数据结构元素之上的操作。一旦这些操作需要修改的话,接受这个操作的数据结构可以保持不变。访问者模式适用于数据结构相对未定的系统,它把数据结构和作用于结构上的操作之间的耦合解脱开,使得操作集合可以相对自由的演化。访问者模式使得增加新的操作变的很容易,就是增加一个新的访问者类。访问者模式将有关的行为集中到一个访问者对象中,而不是分散到一个个的节点类中。当使用访问者模式时,要将尽可能多的对象浏览逻辑放在访问者类中,而不是放到它的子类中。访问者模式可以跨过几个类的等级结构访问属于不同的等级结构的成员类。
- 什么是访问者模式以及特点: 比如我有一个账单,账单有收入,支出两个固定方法。但是访问账单的人不确定,有可能是一个或者多个.频繁的更改数据,但结构不变.比如:虽然每一天账单的数据都会变化.但是只有两类数据,就是支出和收入(结构不变)
- 实例: 账单有支出和收入两种方法.BOSS和cpa两种角色查看账单显示不同的数据.
-
实例代码实现
//创建一个账单接口,有接收访问者的功能 interface Bill { function accept(AccountBookView $viewer); } //消费单子 class ConsumerBill implements Bill { private $item; private $amount; public function __construct(String $item, int $amount) { $this->item = $item; $this->amount = $amount; } public function accept(AccountBookView $viewer) { $viewer->consumer($this); } public function getItem() { return $this->item; } public function getAmount() { return $this->amount; } } //收入单子 class IncomeBill implements Bill { private $item; private $amount; public function __construct(String $item, int $amount) { $this->item = $item; $this->amount = $amount; } public function accept(AccountBookView $viewer) { $viewer->income($this); } public function getItem() { return $this->item; } public function getAmount() { return $this->amount; } } //访问者接口 interface AccountBookView { // 查看消费的单子 function consumer(ConsumerBill $consumerBill); // 查看收入单子 function income(IncomeBill $incomeBill); } // 老板类:访问者是老板,主要查看总支出和总收入 class Boss implements AccountBookView { private $totalConsumer; private $totalIncome; // 查看消费的单子 public function consumer(ConsumerBill $consumerBill) { $this->totalConsumer = $this->totalConsumer + $consumerBill->getAmount(); } // 查看收入单子 public function income(IncomeBill $incomeBill) { $this->totalIncome = $this->totalIncome + $incomeBill->getAmount(); } public function getTotalConsumer() { echo ("老板一共消费了".$this->totalConsumer); } public function getTotalIncome() { echo ("老板一共收入了".$this->totalIncome); } } //会计类:访问者是会计,主要记录每笔单子 class CPA implements AccountBookView { private $count = 0; // 查看消费的单子 public function consumer(ConsumerBill $consumerBill) { $this->count++; if ($consumerBill->getItem() == "消费") { echo ("第" . $this->count . "个单子消费了:" . $consumerBill->getAmount()); } } // 查看收入单子 public function income(IncomeBill $incomeBill) { if ($incomeBill->getItem() == "收入") { echo ("第" . $this->count . "个单子收入了:" . $incomeBill->getAmount()); } } } //账单类:用于添加账单,和为每一个账单添加访问者 class AccountBook { private $listBill; // 添加单子 public function add(Bill $bill) { $this->listBill[] = $bill; } // 为每个账单添加访问者 public function show(AccountBookView $viewer) { foreach ($this->listBill as $value) { $value->accept($viewer); } } } //测试类 class Test { public static function main() { // 创建消费和收入单子 $consumerBill = new ConsumerBill("消费", 3000); $incomeBill = new IncomeBill("收入", 5000); $consumerBill2 = new ConsumerBill("消费", 4000); $incomeBill2 = new IncomeBill("收入", 8000); // 添加单子 $accountBook = new AccountBook(); $accountBook->add($consumerBill); $accountBook->add($incomeBill); $accountBook->add($consumerBill2); $accountBook->add($incomeBill2); // 创建访问者 $boss = new Boss(); $cpa = new CPA(); //boss访问 $accountBook->show($boss); // boss查看总收入和总消费 $boss->getTotalConsumer(); //7000 $boss->getTotalIncome(); //13000 //cpa查看消费和收入明细. $accountBook->show($cpa); } } Test::main();
(十二)空对象模式(Null Object)
- 定义
用一个空对象取代 NULL,减少对实例的检查。这样的空对象可以在数据不可用的时候提供默认的行为解决在需要一个对象时返回一个null值,使其调用函数出错的情况
- 实例: 1)通过抽象类约束的实现空对象模式 2) 使用魔术方法__call实现空对象模式.
-
实例代码实现
//通过抽象约束的空对象 abstract class AbstractCustomer { protected $name; public abstract function isNil():bool; public abstract function getName() : string; } class RealCustomer extends AbstractCustomer { public function __construct(string $name) { $this->name = $name; } public function isNil():bool { return false; } public function getName() : string { return $this->name; } } class NullCustomer extends AbstractCustomer { public function getName() : string { return "Not Available in Customer Database"; } public function isNil():bool { return true; } } class CustomerFactory { public static $users = []; public static function getCustomer($name) { if (in_array($name, self::$users)){ return new RealCustomer($name); } return new NullCustomer(); } } CustomerFactory::$users = ["Rob", "Joe"]; $customer1 = CustomerFactory::getCustomer('Rob'); $customer3 = CustomerFactory::getCustomer('jack'); echo $customer3->getName(); //使用魔术方法__call生成空对象 class Person{ public function code(){ echo 'code makes me happy'.PHP_EOL; } } class NullObject{ public function __call($method,$arg){ echo 'this is NullObject'; } } //定义一个生成对象函数,只有PHPer才允许生成对象 function getPerson($name){ if($name=='PHPer'){ return new Person; }else{ return new NullObject; } } $phper = getPerson('phper'); $phper->code();
(十三)规格模式(Specification)
- 定义
构建一个清晰的业务规则规范,其中每条规则都能被针对性地检查。每个规范类中都有一个称为 isSatisfiedBy 的方法,方法判断给定的规则是否满足规范从而返回 true 或 false。
- 实例: 用 与,或,非 三种方式检验价格是否达标
-
实例代码实现
class Item { /** * @var float */ private $price; public function __construct(float $price) { $this->price = $price; } public function getPrice(): float { return $this->price; } } interface SpecificationInterface { public function isSatisfiedBy(Item $item): bool; } class OrSpecification implements SpecificationInterface { /** * @var SpecificationInterface[] */ private $specifications; /** * @param SpecificationInterface[] ...$specifications */ public function __construct(SpecificationInterface ...$specifications) { $this->specifications = $specifications; } /** * 如果有一条规则符合条件,返回 true,否则返回 false */ public function isSatisfiedBy(Item $item): bool { foreach ($this->specifications as $specification) { if ($specification->isSatisfiedBy($item)) { return true; } } return false; } } class PriceSpecification implements SpecificationInterface { /** * @var float|null */ private $maxPrice; /** * @var float|null */ private $minPrice; /** * @param float $minPrice * @param float $maxPrice */ public function __construct($minPrice, $maxPrice) { $this->minPrice = $minPrice; $this->maxPrice = $maxPrice; } public function isSatisfiedBy(Item $item): bool { if ($this->maxPrice !== null && $item->getPrice() > $this->maxPrice) { return false; } if ($this->minPrice !== null && $item->getPrice() < $this->minPrice) { return false; } return true; } } class AndSpecification implements SpecificationInterface { /** * @var SpecificationInterface[] */ private $specifications; /** * @param SpecificationInterface[] ...$specifications */ public function __construct(SpecificationInterface ...$specifications) { $this->specifications = $specifications; } /** * 如果有一条规则不符合条件,返回 false,否则返回 true */ public function isSatisfiedBy(Item $item): bool { foreach ($this->specifications as $specification) { if (!$specification->isSatisfiedBy($item)) { return false; } } return true; } } class NotSpecification implements SpecificationInterface { /** * @var SpecificationInterface */ private $specification; public function __construct(SpecificationInterface $specification) { $this->specification = $specification; } public function isSatisfiedBy(Item $item): bool { return !$this->specification->isSatisfiedBy($item); } } $spec1 = new PriceSpecification(50, 100); $spec2 = new PriceSpecification(50, 200); $orSpec = new OrSpecification($spec1, $spec2); echo $orSpec->isSatisfiedBy(new Item(60)); $andSpec = new AndSpecification($spec1, $spec2); echo $andSpec->isSatisfiedBy(new Item(60)); $notSpec = new NotSpecification($spec1); echo $notSpec->isSatisfiedBy(new Item(60));
本作品采用《CC 协议》,转载必须注明作者和本文链接