PHP常用设计模式单例, 工厂, 观察者, 责任链, 装饰, 策略,适配,桥接模式
程序员文章站
2024-01-07 12:55:16
...
// 多态, 在JAVA中是这样用的, 其实在PHP当中可以自然消除, 因为参数是动态的, 你传什么过来都可以, 不限制类型, 直接调用类的方法 abstract class Tiger { public abstract function climb(); } class XTiger extends Tiger { public function climb() { echo '摔下来'; } } class MTiger extends Tiger { public function climb() { echo '爬到树顶'; } } class Client { public static function call(Tiger $animal) { $animal->climb(); } } Client::call(new XTiger()); Client::call(new MTiger());
// 面向对象里面有一个面向接口开发, 就是一个共同的规格, 你生产插座, 我生产插头 // 共同接口 interface db { function conn(); } // 服务端开发(不知道将会被谁调用) class dbmysql implements db{ public function conn() { echo '连上了MySQL'; } } class dbsqlite implements db{ public function conn() { echo '连上了sqlite'; } } // 客户端, 看不到dbmysql, dbsqlite的内部细节的, 只知道上两个类实现了db接口. $db = new dbmysql(); $db->conn(); // 因为知道这个类实现了db接口, 所以知道有这个conn类 $sqlite = new dbsqlite(); $sqlite->conn(); // 我连我有哪些类我都不希望告诉客户端, 那么怎么进一步进行封装 // 发生连接的双方知道的越少越好, 你还知道我有两个类
// 简单工厂 // 面向对象里面有一个面向接口开发, 就是一个共同的规格, 你生产插座, 我生产插头 // 共同接口 interface db { function conn(); } // 服务端开发(不知道将会被谁调用) class dbmysql implements db{ public function conn() { echo '连上了MySQL'; } } class dbsqlite implements db{ public function conn() { echo '连上了sqlite'; } } // 简单工厂 class Factory { public static function createDB($type) { if($type == 'mysql') { return new dbmysql(); } elseif ($type=='sqlite') { return new dbsqlite(); } else { throw new Exception("Error db type", 1); } } } // 客户端现在不知道对方到底有哪些类名了 // 只知道对方开放了一个Factory::createDB方法 // 方法允许传递数据库名称 $mysql = Factory::createDB('mysql'); $mysql->conn(); $sqlite = Factory::createDB('sqlite'); $sqlite->conn(); // 原本你知道服务器端的两个类名, 觉得你知道的太多, 再封装起来, 只给一个通道, 更好的适应变化 // 如果以后新增oracle类型, 怎么办? // 我们PHP随手改了就行, 像JAVA打包一次很麻烦, 又要修改服务端内容 // 服务端要修改Factory的内容(在java, c++中, 改后还得再编译) // 这个时候就想如何来改进呢 // 在OOD(面向对象设计)的法则中, 有重要的开闭原则--对于修改是封闭的, 对于扩展是开放的. // 你可以新增一个源代码, 不要修改旧的代码 //现在用第二种办法, 叫工厂方法
// 共同接口 interface db { function conn(); } interface Factory { function createDB(); } // 服务端开发(不知道将会被谁调用) class dbmysql implements db{ public function conn() { echo '连上了MySQL'; } } class dbsqlite implements db{ public function conn() { echo '连上了sqlite'; } } class mysqliFactory implements Factory{ public function createDB() { return new dbmysql(); } } class sqliteFactory implements Factory { public function createDB() { return new dbsqlite(); } } // 服务器端添加oracle类 // 前面的代码不用改 // 我就新增一个数据库驱动类和一个工厂, 这样就避免了对源代码的修改 class dboracle implements db { public function conn() { echo '连接上了oracle'; } } class oracleFactory implements Factory { public function createDB() { return new dboracle(); } } // 客户端开始, 对方给了两个api, 一个db的api, 一个是创造数据库的api $fact = new mysqliFactory(); $db = $fact->createDB(); $db->conn(); $fact = new sqliteFactory(); $db = $fact->createDB(); $db->conn();
// 单例 // 一般来说一个中小型网站一个db去连接数据库就行了 // 想想, 能不能只有一个db类, 一个upload类, 一个cookie类 // 类肯定只有一个, 如何保证类的实例也只有一个 // 2个对象是同一个的时候才全等 // 单例模式 class single { protected static $ins = null; // 控制权限, *new操作, 把大门关上了, 需要留一个小窗户 // 方法前加final, 则方法不能被覆盖, 类前加final, 则类不能被继承 final protected function __construct() {} // 防clone final protected function __clone(){} //留一个接口来new对象 public static function getIns() { if(self::$ins == null) { self::$ins = new self(); } return self::$ins; } } $s1 = single::getIns(); $s2 = single::getIns(); if ($s1 === $s2) { echo '是一个对象'; } else { echo '不是一个对象'; }
<!DOCTYPE HTML> <html> <head> <title>colour_blue</title> <meta name="description" content="website description" /> <meta name="keywords" content="website keywords, website keywords" /> <meta http-equiv="content-type" content="text/html; charset=utf-8" /> <style> div{ margin:10px; width:500px; height:200px; border: 1px solid green; } #content { } #ad { } </style> <script> function t() { var sel = document.getElementsByTagName('select')[0]; // alert(sel.value); if(sel.value == 'male') { document.getElementById('content').style.backgroundColor = 'gray'; document.getElementById('ad').innerHTML = '汽车'; } else if(sel.value=='female') { document.getElementById('content').style.backgroundColor = 'pink'; document.getElementById('ad').innerHTML = '减肥'; } } /** * * 观察上述代码, 如果新增了一个study区, 在切换时, 学习内容也要随之切换, 那么, 就需要改动T函数! 对于修改又开放了. * 思考---如果需要新监听footer区, 如何能不改原来的代码, 而只增加 */ </script> </head> <body> <h1>面向过程, 不用任务来切换</h1> <select name="" id="" onchange="t()"> <option value="male">男式风格</option> <option value="female">女式风格</option> </select> <div id="content">我是内容</div> <div id="ad">我是广告</div> </body> </html>
// 比如登录, 在登录的时候出于安全考虑要记录上次登录时间 // 出于商业考虑, 给你推荐商品 class user implements SplSubject { public $lognum; public $hobby; protected $observers = null; public function __construct($hobby) { $this->lognum = rand(1, 10); $this->hobby = $hobby; $this->observers = new SplObjectStorage(); } public function login() { // 操作session $this->notify(); } public function attach(SplObserver $observer) { $this->observers->attach($observer); } public function detach(SplObserver $observer) { $this->observers->detach($observer); } public function notify() { $this->observers->rewind(); while($this->observers->valid()) { $observer = $this->observers->current(); $observer->update($this); $this->observers->next(); } } } class secrity implements SplObserver { public function update(SplSubject $subject) { if($subject->lognum < 3) { echo '这是第'.$subject->lognum.'次安全登录'; } else { echo '这是第'.$subject->lognum.'次登录,异常'; } } } class ad implements SplObserver { public function update(SplSubject $subject) { if($subject->hobby == 'sports') { echo '*英锦赛'; } else { echo '好好学习天天向上'; } } } // 实施观察 $user = new user('sports'); $user->attach(new secrity()); $user->attach(new ad()); $user->login();
<!DOCTYPE HTML> <html> <head> <title>colour_blue</title> <meta name="description" content="website description" /> <meta name="keywords" content="website keywords, website keywords" /> <meta http-equiv="content-type" content="text/html; charset=utf-8" /> <style> div{ margin:10px; width:500px; height:200px; border: 1px solid green; } #content { } #ad { } </style> </head> <body> <h1>观察者模式</h1> <select name="" id=""> <option value="male">男式风格</option> <option value="female">女式风格</option> </select> <input type="button" onclick="t1()" value="观察尾部" /> <input type="button" onclick="t2()" value="别观察尾部了" /> <div id="content">我是内容</div> <div id="ad">我是广告</div> <div id="study">学习</div> <script> // 服务器端 var sel = document.getElementsByTagName('select')[0]; sel.observers = {}; sel.attach = function(key, obj) { sel.observers[key] = obj; } sel.detach = function(key) { delete this.observers[key]; } sel.onchange = sel.notify = function(){ for(var key in this.observers) { this.observers[key].update(this); } } // 客户端 var content = document.getElementById('content'); content.update = function(observer) { if(observer.value == 'male'){ this.style.backgroundColor = 'gray'; } else if(observer.value=='female'){ this.style.backgroundColor = 'pink'; } } sel.attach('content', content); var ad = document.getElementById('ad'); ad.update = function(observer) { if(observer.value == 'male'){ this.innerHTML = '汽车'; } else if(observer.value=='female'){ this.innerHTML = '减肥'; } } sel.attach('ad', ad); // 客户端和服务端实现了解耦 // 后面有一个学习区, 如果状态改变了, 学习区也要变化 var study = document.getElementById('study'); study.update = function(observer) { if(observer.value == 'male'){ this.innerHTML = '学习计算机'; } else if(observer.value=='female'){ this.innerHTML = '学习美容'; } } sel.attach('study', study); //如果想不观察尾部了 function t1() { sel.atach('study', study); } function t2() { sel.detach('study'); } </script> </body> </html>
.html <!DOCTYPE HTML> <html> <head> <title>colour_blue</title> <meta name="description" content="website description" /> <meta name="keywords" content="website keywords, website keywords" /> <meta http-equiv="content-type" content="text/html; charset=utf-8" /> <style> div{ margin:10px; width:500px; height:200px; border: 1px solid green; } #content { } #ad { } </style> </head> <body> <h2>责任链模式举报过程</h2> <form action="10.php" method="post"> <select name="jubao" id=""> <option value="1">粗口</option> <option value="2">黄赌毒</option> <option value="3">分裂国家</option> </select> <button type="submit">举报</button> </form> </body> </html> .php <?php header('Content-type: text/html; charset=utf-8'); //版主 class board { protected $power = 1; protected $top = 'admin'; public function process($lev) { if($lev <= $this->power) { echo '版主删贴'; } else { $top = new $this->top; $top->process($lev); } } } class admin { protected $power = 2; protected $top = 'police'; public function process($lev) { if($lev <= $this->power) { echo '管理员封账号'; } else { $top = new $this->top; $top->process($lev); } } } class police { protected $power; protected $top = null; public function process($lev) { echo '抓起来'; } } $lev = $_POST['jubao']+0; $judge = new board(); $judge->process($lev);
.html <!DOCTYPE HTML> <html> <head> <title>colour_blue</title> <meta name="description" content="website description" /> <meta name="keywords" content="website keywords, website keywords" /> <meta http-equiv="content-type" content="text/html; charset=utf-8" /> <style> div{ margin:10px; width:500px; height:200px; border: 1px solid green; } #content { } #ad { } </style> </head> <body> <h2>面向过程完成举报过程</h2> <form action="09.php" method="post"> <select name="jubao" id=""> <option value="1">粗口</option> <option value="2">黄赌毒</option> <option value="3">分裂国家</option> </select> <button type="submit">举报</button> </form> </body> </html> .php <?php header('Content-type: text/html; charset=utf-8'); $lev = $_POST['jubao']; //版主 class board { public function process() { echo '版主删贴'; } } class admin { public function process() { echo '管理员封账号'; } } class police { public function process() { echo '抓起来'; } } if ($lev == 1) { $judge = new board(); $judge->process(); } elseif($lev==2) { $judge = new admin(); $judge->process(); } elseif($lev==3) { $judge = new police(); $judge->process(); } // 处理的不够优雅, 现在还是面向过程的写法, 面向过程和面向对象混合了 // 如果以后在两个等级中间要添加一个等级应该怎么办
.html <!DOCTYPE HTML> <html> <head> <title>colour_blue</title> <meta name="description" content="website description" /> <meta name="keywords" content="website keywords, website keywords" /> <meta http-equiv="content-type" content="text/html; charset=utf-8" /> <style> div{ margin:10px; width:500px; height:200px; border: 1px solid green; } #content { } #ad { } </style> </head> <body> <h2>策略模式 和工厂方法的区别只是在逻辑上的区别, 也叫聚合</h2> <form action="11.php" method="post"> <input type="text" name="op1" /> <select name="op" id=""> <option value="add">+</option> <option value="sub">-</option> <option value="mul">*</option> <option value="div">/</option> </select> <input type="text" name="op2" /> <button type="submit">计算</button> </form> </body> </html> .php <?php /** * Created by PhpStorm. * User: michaeldu * Date: 15/7/15 * Time: 上午12:11 */ interface Math { public function calc($op1, $op2); } class MathAdd implements Math{ public function calc($op1, $op2) { return $op1 + $op2; } } class MathSub implements Math{ public function calc($op1, $op2) { return $op1 - $op2; } } class MathMul implements Math{ public function calc($op1, $op2) { return $op1 * $op2; } } class MathDiv implements Math{ public function calc($op1, $op2) { return $op1 / $op2; } } //封装一个虚拟计算器, 统一计算接口 class CMath { protected $calc = null; public function __construct($type) { $calc = 'Math'. ucfirst($type); $this->calc = new $calc(); } public function calc($op1, $op2) { return $this->calc->calc($op1, $op2); } } $type = $_POST['op']; $cmath = new CMath($type); echo $cmath->calc($_POST['op1'], $_POST['op2']);
// 装饰器模式 decorator class article { protected $content; public function __construct($content) { $this->content = $content; } public function decorator() { return $this->content; } } $art = new article("好好学习"); echo $art->decorator(); // 文章需要小编加摘要 class BianArticle extends article { public function summary() { return $this->content.'小编加了摘要'; } } $art = new BianArticle('好好学习'); echo $art->summary(); // 又请了SEO人员, 要对文章进行description处理... class SEOArticle extends BianArticle { public function seo() { //... } } // 又有了广告部 class ADArticle extends SEOArticle { // 层次越来越深, 目的却只是给文章加各种内容 }
// 装饰器模式做文章修饰功能 class BaseArt { protected $content; protected $art; public function __construct($content) { $this->content = $content; } public function decorator() { return $this->content; } } //编辑文章摘要 class BianArt extends BaseArt { public function __construct(BaseArt $art) { $this->art = $art; } public function decorator() { return $this->content = $this->art->decorator() . '小编摘要'; } } // SEO class SEOArt Extends BaseArt { public function __construct(BaseArt $art) { $this->art = $art; } public function decorator() { return $this->content = $this->art->decorator() . 'SEO关键词'; } } $b = new SEOArt(new BianArt(new BaseArt('天天向上'))); echo $b->decorator(); $b = new BianArt(new SEOArt(new BaseArt('天天向上'))); echo $b->decorator(); // 装饰的先后顺序可以随便换, 新增多少个装饰者都很方便 // 基类负责创建对象 // 子类负责装饰
// 适配器模式 // 服务器端代码 class weather { public static function show() { $today = [ 'tep' => 28, 'wind' => 7, 'sun' => 'sunny', ]; return serialize($today); } } // 客户端调用 $weather = unserialize(weather::show()); echo '温度: ',$weather['tep'],'<br />'; echo '风力: ',$weather['wind'],'<br />'; echo 'sun: ',$weather['sun'],'<br />'; // 来了一批手机上的java客户端 , 不认识PHP的串化行后的字符串, 怎么办? // 把服务器端代码改了? 旧的客户端会受影响 // 旧的类和函数都不让修改, 如何来进行扩展 // 增加一个适配器 class AdapterWeather extends weather { public static function show() { //拿到旧数据 $today = parent::show(); // 然后开始转换 $today = unserialize($today); $today = json_encode($today); return $today; } } // ==== java, python再来调用就不怕了, 通过适配器调用 $weather = AdapterWeather::show(); $w = json_decode($weather); echo '温度: ',$w->tep,'<br />'; echo '风力: ',$w->wind,'<br />'; echo 'sun: ',$w->sun,'<br />'; // 没有修改旧的方法, 这样就没有影响旧的客户端 // 没有修改旧的类, 说明没有违反开闭原则
// 桥接模式 bridge 尝试 // 论坛给用户发信息, 可以是站内短信, email, 手机 // 本来只需要发站内短信, 后来又需要发email // 一个类只做一件事, 我们把他做成一个接口或者抽象类, 具体的发送由具体的子类实现 interface msg { public function send($to, $content); } class zn implements msg { public function send($to, $content) { echo '站内信给', $to, ' 内容是: ', $content; } } // 有一天要改造, 要发email class email implements msg { public function send($to, $content) { echo 'email给', $to, ' 内容是: ', $content; } } // 后来要发短信 class sms implements msg { public function send($to, $content) { echo '短信给', $to, ' 内容是: ', $content; } } // 后来内容也丰富了, 分普通, 加急, 特急 // 子类就爆炸了, 像数据的三范式一样, 如果完全遵循解耦是不行了 /* class zncommon extends zn; class znwarn extends zn; class zndanger extends zn; class emailcomm extends email; class emailwarn extends email; class emaildanger extends email; .... */ /** * 思考: * 信的发送方式是一个变化因素 * 信的紧急程度是一个变化因素, * 为了不修改父类, 只好考虑2个因素的组织, 不停产生新类.... */
abstract class info { protected $send = null; public function __construct($send) { $this->send = $send; } abstract public function msg($content); public function send($to, $content){ $content = $this->msg($content); $this->send->send($to, $content); } } class zn { public function send($to, $content) { echo '站内给: ', $to, '内容是: ', $content; } } class email { public function send($to, $content) { echo 'email给: ', $to, '内容是: ', $content; } } class sms { public function send($to, $content) { echo 'sms给: ', $to, '内容是: ', $content; } } class commoninfo extends info{ public function msg($content) { return '普通'. $content; } } class warninfo extends info{ public function msg($content) { return '紧急'.$content; } } class dangerinfo extends info{ public function msg($content) { return '紧急'.$content; } } // 用站内发普通信息 $commoninfo = new commoninfo(new zn()); $commoninfo->send('小明', '吃饭了'); //给小刚发紧急手机短信消息 $dangerinfo = new dangerinfo(new sms()); $dangerinfo->send('小刚', '失火了'); // 本来需要3x3个类 // 现在只需要3+3个类了 // 现在耦合了, view中不写逻辑, 数据库三范式, 老有相同的字段, 为了速度, 之前的设计模式都是在解耦, 现在反而增加了耦合, 是为了防止子类爆炸