初探PHP设计模式
设计模式不是一套具体的语言框架,是行之有效的编码规范,是一套被反复使用、多数人知晓的、经过分类的、代码设计经验的总结。使用设计模式的目的:为了代码可重用性、让代码更容易被他人理解、保证代码可靠性。合理使用设计模式将有助于初学者更加深入地理解面向对象思维。
一、三大基本模式
1、工厂模式
工厂模式分为简单工厂模式,工厂模式,抽象工厂模式,今天主要讲简单工厂模式。通过定义好的工厂方法或者类生成对象,而不是在代码中直接new 一个类来生成对象。使用工厂模式,可以避免当改变某个类的名字或者方法之后,在调用这个类的所有的代码中都修改它的名字或者参数。
声明一个初始类
class DataBase { public function __construct() { echo __CLASS__; } }
创建一个工厂类,写一个工厂方法
class Factory { static function creatDataBase() { $db = new DataBase(); return $db; } }
再在需要调用初始类的php代码处使用工厂类
spl_autoload_register(‘autoload1‘); $db = SPL\Factory::creatDataBase(); function autoload1($class) { $dir = __DIR__; $requireFile = $dir . "\\" . $class . ".php"; require $requireFile; }
之后如果要修改初始类的信息,如名称,就可以直接修改与之对应的工厂类中的代码。
结果如下:
2、单例模式
在应用程序执行的时候,只能获得一个对象实例。主要是为了防止对资源的浪费,就比如数据库类的连接方法,普通情况下,多个类使用数据库就需要创建多个数据库对象,而实际情况数据库的对象只需要创建一次就可以在不同类中使用。
单例模式的要求:
1,不允许从外部调用以防止创建多个对象,需要将构造函数和析构函数必须声明为私有。
2,防止实例被克隆,这会创建对象的副本,需要将clone方法声明为私有
3,只有getInstance()方法为公有的。
namespace SPL; class Singleton { private static $instance; private static $num = 0; private function __construct() { echo "创建次数:", self::$num + 1; } /*只有第一次调用时声明类获取实例*/ private function __destruct() { } private function __clone() { } public static function getInstance() { if (self::$instance == null) { self::$instance = new self(); } return self::$instance; } }
调用:
$db=SPL\Singleton::getInstance(); $db=SPL\Singleton::getInstance(); $db=SPL\Singleton::getInstance();
结果如下:
3、注册模式
之前已经创建好的对象,可以储存在到某个全局可以使用的对象树上,在需要使用的时候,直接从该对象树上获取即可,并且任何地方直接去访问。通过工厂模式和单例模式创建对象,而使用注册模式去更好的使用对象。注册模式常常和工厂模式一起使用。
namespace SPL; class Register { protected static $object; public static function set($alias,$object){ self::$object[$alias]=$object; } public static function get($alias) { return self::$object[$alias]; } public static function _unset($alias) { unset(self::$object[$alias]); } }
在工厂模式初始化的时候,将对象注册进去
namespace SPL; class Factory { static function creatDataBase() { $db = new DataBase(); Register::set(‘FtyDb‘,$db); } }
使用工厂模式产生的对象时就可以直接调用注册模式了
SPL\Factory::creatDataBase(); $db=SPL\Register::get(‘FtyDb‘);
结果如下:
4,适配器模式
将截然不同的函数接口封装成统一的API。PHP中的数据库操作有MySQL,MySQLi,PDO三种,可以用适配器模式统一成一致,使不同的数据库操作,统一成一样的API。
namespace \SPL\DataBase; //接口 interface IDataBase { public function conn($host, $user, $pwd, $dbname); public function query($query); public function close(); } //mysql class Mysql implements IDataBase { protected $conn; public function conn($host, $user, $pwd, $dbname) { $this->conn = mysql_connect($host, $user, $pwd); mysql_select_db($dbname, $this->conn); } public function query($query) { return mysql_query($query); } public function close() { mysql_close($this->conn); } } //mysqli namespace SPL\DataBase; class Mysqli implements IDataBase { protected $conn; public function conn($host,$user,$pwd,$dbname) { $this->conn=mysqli_connect($host,$user,$pwd,$dbname); } public function query($query) { return mysqli_query($this->conn,$query); } public function close() { mysqli_close($this->conn); } } //PDO namespace SPL\DataBase; class PDO implements IDataBase { protected $conn; public function conn($host, $user, $pwd, $dbname) { $this->conn = new \PDO("mysql:host=$host;dbname=$dbname", $user, $pwd); } public function query($query) { $this->conn->query($query); } public function close() { unset($this->conn); } }
接口中的方法要和继承该接口的类的方法相同。然后在需要数据库操作的地方使用该接口
require(‘SPL\DataBase\IDataBase.php‘); $db=new \SPL\DataBase\Mysqli(); $res=$db->conn(‘localhost‘,‘root‘,‘root‘,‘mrmf‘); $result=$db->query(‘show columns from user‘); $rows = $result->fetch_array(); print_r($rows); $db->close();
结果如下:
5,策略模式
将一组特定的行为和算法封装成类,以适应某些特定的上下文环境。
声明策略的接口文件,定义接口的实现类
//接口 namespace SPL\Strategy; interface UserStrategy { function showAd(); function showCategory(); } //女士用户 class FemaleUser implements UserStrategy { public function showAd() { echo "2022长袖连衣裙<br>"; } public function showCategory() { echo "女装"; } } //男士用户 class MaleUser implements UserStrategy { public function showAd() { echo "2019清商务休闲裤<br>"; } public function showCategory() { echo "男装"; } }
class Page { private $strategy; public function index() { echo "name:", $this->strategy->showAd(); echo "category:", $this->strategy->showCategory(); } public function setStrategy(SPL\Strategy\UserStrategy $strategy) { $this->strategy = $strategy; } }
调用
$page = new Page(); if (isset($_GET[‘female‘])) { $strategy = new SPL\Strategy\FemaleUser(); } else { $strategy = new SPL\Strategy\MaleUser(); } $page->setStrategy($strategy); $page->index();
结果如下:
6,数据对象映射模式
将对象和数据储存映射起来,对一个对象的操作会映射为对数据存储的操作,例如tp3的M()方法。
//创建一个验证码表 CREATE TABLE `verify` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT ‘主键‘, `sort` smallint(6) NOT NULL COMMENT ‘验证码类型‘, `phone` varchar(20) NOT NULL COMMENT ‘手机号‘, `code` varchar(6) NOT NULL COMMENT ‘验证码‘, `ctime` varchar(19) NOT NULL COMMENT ‘发送时间‘, `num` smallint(6) NOT NULL DEFAULT ‘1‘ COMMENT ‘每日发送次数‘, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT=‘验证码表‘; DataObject.php : //创建一个与验证码表对应的类DataObject,添加一些方法。 namespace SPL; class DataObject { //声明类属性和数据表中的字段映射 public $db; public $id; public $sort; public $phone; public $code; public $ctime; public $num; //构造方法实现数据表的查询功能 public function __construct($id) { $this->db = new DataBase\Mysqli(); $this->db->conn(‘localhost‘, ‘root‘, ‘root‘, ‘mrmf‘); $res = $this->db->query("select * from verify where id=‘{$id}‘ limit 1"); $data = $res->fetch_assoc(); $this->id = $data[‘id‘]; $this->sort = $data[‘sort‘]; $this->phone = $data[‘phone‘]; $this->code = $data[‘code‘]; $this->ctime = $data[‘ctime‘]; $this->num = $data[‘num‘]; } //单独修改sort为默认值 public function setDefSort($id,$value) { $this->db = Factory::createDataObject($id); $this->sort =$value; } public function setDefPhone($id,$value) { $this->db = Factory::createDataObject($id); $this->sort = $value; } //析构方法为数据表的修改功能 public function __destruct() { $this->db->query("update verify set sort={$this->sort},phone={$this->phone},code={$this->code},ctime={$this->ctime},num={$this->num} where id={$this->id}"); } } Factory : //添加关于DataObject类的工厂方法 static function createDataObject($id) { $key = ‘Db‘ . $id; $db = Register::get($key); if (!$db) { $db = new DataBase\Mysqli(); $db->conn(‘localhost‘, ‘root‘, ‘root‘, ‘mrmf‘); Register::set(‘Db‘ . $id, $db); } return $db; }
通过调用工厂方法实现操作数据表的功能
$db = new SPL\DataObject(1); $db->setDefSort(1,12345);
结果如下
7,观察者模式
当一个对象状态发生变化时,依赖它的对象全部会收到通知,并自动更新。当用户登录事件发生后,要执行一连串操作。比如根据vip判断是否显示广告,根据是否是新用户显示优惠活动。传统的编程方式,就是在登录的代码之后直接加入处理的逻辑。当登录后的操作增多后,代码会变得难以维护。这种方式是耦合的,侵入式的,增加新的逻辑需要修改事件的主体代码。而观察者模式实现了低耦合,非侵入式的通知与更新机制。
Observer.php //定义一个观察者接口 interface Observer { public function update($id); } YnVip.php //定义两个具体的观察者类 class YnVip implements Observer { public function update($id=‘‘) { echo empty($id)?‘no vip‘:‘yes vip‘,‘<br>‘; } } YnNewUser.php class YnNewUser implements Observer { public function update($id=‘‘) { echo empty($id) ? ‘no new user‘ : ‘yes new user‘,‘<br>‘; } } commom.php //定义一个基类作为事件触发类 class Common { private $observer; public $id; //新增观察者 function addObserver(Observer $observer){ $this->observer[]=$observer; } //通知各个观察者(执行观察者中的方法) function notify(){ foreach ($this->observer as $observer) { $observer->update($this->id); } } } //定义一个继承了Common类的子类 class Login extends Common { public function login($id=‘‘){ echo "执行登录",‘<br>‘; $this->id=$id; } }
我们可以看见,观察者就是定义各种功能的对象,当我们需要该功能时,就可以使用Common类中的添加观察者方法添加观察者
$user=new SPL\Observer\Login(); $user->login(); $user->addObserver(new YnVip()); $user->addObserver(new YnNewUser()); $user->notify();
结果如下:
当需要添加其它功能时,我们不必要耦合的在事件主体中添加,只需增加一个观察者类,再动态地在事件主体添加调用观察者对象的语句就可以了
8、原型模式
与工厂模式类似,都是用来创建对象;与工厂模式的实现不同,原型模式是先创建好一个原型对象,然后通过clone原型对象来创建新的对象。这样就免去了类创建时重复的初始化操作;在这些场景我们可以使用原型模式:
1,大对象的创建,创建一个大对象需要很大的开销,如果每次new就会消耗很大,原型模式仅需要内存拷贝即可。
2,一个对象的初始化需要很多其他对象的数据准备或其他资源的繁琐计算,那么可以使用原型模式。
3,当需要一个对象的大量公共信息,少量字段进行个性化设置的时候,也可以使用原型模式拷贝出现有对象的副本进行加工处理。
class Prototype { private $data; function __construct() { echo "调用class Prototype类<br>"; } function init($width = 20, $height = 10) { $data = array(); for ($i = 0; $i < $height; $i++) { for ($j = 0; $j < $width; $j++) { $data[$i][$j] = ‘*‘; } } $this->data = $data; } function rect($x1, $y1, $x2, $y2) { foreach ($this->data as $k1 => $line) { if ($x1 > $k1 or $x2 < $k1) continue; foreach ($line as $k2 => $char) { if ($y1 > $k2 or $y2 < $k2) continue; $this->data[$k1][$k2] = ‘ ‘; } } } function draw() { foreach ($this->data as $line) { foreach ($line as $char) { echo $char; } echo "<br>"; } } }
$prototype = new SPL\Prototype(); $prototype->init(); $prototype1=clone $prototype; $prototype->rect(1,3,4,6); $prototype->draw(); echo ‘—————————————————<br>‘; $prototype2=clone $prototype; $prototype->rect(2,6,3,8); $prototype->draw();
结果如下:
注意,在我们克隆对象时并不会执行构造方法,因为克隆是在堆中进行的,而在类加载流程中才会调用构造函数,最后生成的对象会放到堆中。
9,装饰器模式
作为一种结构型模式, 装饰器(Decorator)模式就是对一个已有结构增加"装饰"。这些‘‘装饰‘可以理解为新增的功能模块。如果要在一个类中的某些功能模块添加一些额外的功能,传统的编程模式是,写一个子类继承它,然后重写实现类的方法。而使用装饰器模式,我们只需要在功能实现前添加一个装饰器对象就可以实现新的功能。
装饰模式和观察者模式在代码实现部分相差不多。