什么是设计模式
设计模式,是一种解决问题的思维,而并非某种特定的方法。是前人给我们总结的宝贵经验。学习设计模式是为了编写可复用、可拓展、高性能软件。学习设计模式关键是要理解,理解方法,理解思想和观念。设计模式是熟练运用OOP后自然而然形成的代码习惯。达到最高境后只有一句话:高内聚、低耦合。
php中的设计模式
想要成为一名高级程序员,设计模式是必须完全掌握的。我们经常看到关于java,c#设计模式的讲解,却很少看到用php代码讲解设计模式的, 这是为什么呢。
jave、c#它们是纯面向对象编程的语言,纯面向对象的编程语言是以类为基本单位,把所有功能封装在类中,真正实现数据和业务逻辑的封装。而设计模式是面向对象编程的高级实践,所以设计模式是在这些纯面向对象语言中最早总结出来的。php本身是一种面向过程编程的的语言,PHP 5中借鉴了java的一些特性开始对面向对象支持更加完善,设计模式也可以用与php中了,但是现在关于php面向对象编程的资料很少,对很多phper进阶高级程序员造成很大障碍,所以我打算写一系列设计模式的文章,一来是对自己的一种提高,二来希望能帮助到那些像我一样没有其他语言基础,php作为的入门语言的程序员。
系列文章将介绍php常用的11中设计模式,本篇将结合代码介绍最基础的三种,工厂模式、单例模式和注册树模式。
学习设计模式之前希望大家能已经熟练掌握了php的一些高级特性,比如命名空间,链式调用,类的自动载入。如果觉得自己的oop学的很渣,可以把以前学习的视频再看一遍,说不定你会对以前一些不懂的知识点豁然开朗呢。
1.工厂模式
工厂模式是用工厂方法生成对象,而不是直接new一个对象。
假设我们在Imooc命名空间下有一个名叫Db的数据库操作类,用普通的方法,如果我们想去创建一个Db的对象,我们会直接new一个出来。
$db = new Imooc\Db();
工厂模式就是用一个工厂方法替换掉直接new一个对象的操作,以后想创建对象就调用这个工厂方法。
<?php
namespace Imooc;
class Factory{
static public function createDb(){
$db = new Db();
return $db;
}
}
$db = Imooc\Factory::createDb();
工厂模式有什么好处呢, 我们的项目中多处都对Db类进行了new的操作,如果这个类发生了一些更改,比如说类名或者是参数的改变,没用工厂模式的话我们就需要进行多处更改,而工厂模式只需要改这个工厂类就行了。
2.单例模式
单例模式使某个类的对象仅能创建一次,通常一个项目中会多次用的Db这个数据库连接类,如果在每个地方都调用工厂方法创建一个数据库连接类,这样是比较消耗资源的,我们只需要一个数据库连接,单例模式就是来解决这个问题的。
我们打开Db类,首先把构造方法设置为私有的,这样就禁止了在其他地方直接new我们的Db类
<?php
namespace Imooc;
class Db{
protected $db;
private function __construct(){
}
static public function getInstance(){
//条件判断Db类是否已经new过
if (self::$db) {
return self::$db;
} else {
//构造方法被设置为了私有的,外部不能直接new,但自己内部可以new
self::$db = new self();
return self::$db;
}
}
}
Db类的构造方法设置为了私有的,那我们在工厂类中也不能直接new了,现在来修改我们的工厂类
<?php
namespace Imooc;
class Factory{
static public function createDb(){
$db = Db::getInstance;
return $db;
}
}
现在不管我们调用多少次工厂方法,我们的数据库连接都只会被创建一次。
3.注册树模式
注册树模式可以把我们的对象放在全局的树上,让对象可以全局共享
下面我们来编写一个注册树的类,包含set,get,_unset三个操作。
<?php
namespace Imooc;
class Register{
protected static $objects;
static public function set($alias,$object){
self::$objedts[$alias] = $object;
}
static public function get($name){
return self::$objedts[$name];
}
//unset是php中的关键词,所以起名为_unset
static public function _unset($alias){
unset(self::$objedts[$alias]);
}
}
下面我们再把工厂方法改一下
<?php
namespace Imooc;
class Factory{
static public function createDb(){
$db = Db::getInstance;
//把单例模式生成的对象放在注册树上
Register::set('db',$db);
}
}
注册树模式(Registry Pattern ):注册树模式为应用中经常使用的对象创建一个中央存储器来存放这些对象 —— 通常通过一个只包含静态方法的抽象类来实现(或者通过单例模式)。也叫做注册器模式
(一)为什么需要注册树模式
解决常用对象的存放问题,实现类似于全局变量的功能。
(二)注册树模式UML图
暂无,跪求提供
(三)简单实例
<?php //User类用于测试 class User{} //注册树类 class Register { protected static $objects; //用于存放实例 //存入实例方法 static public function set($key, $object) { self::$objects[$key] = $object; } //获取实例方法 static public function get($key) { if (!isset(self::$objects[$key])) { return false; } return self::$objects[$key]; } //删除实例方法 static public function _unset($key) { unset(self::$objects[$key]); } } $user = new User; //存入实例 Register::set('User',$user); //查看实例 var_dump(Register::get('User')); //删除实例 Register::_unset('User'); //再次查看实例 var_dump(Register::get('User'));
注册树经常与单例模式一起使用,先查看注册树上是否有该实例,有就直接使用,没有就生成一个实例,并挂到树上。有些时候我们还可以这样做,让get方法如果get不到实例的时候就自动new一个存放起来,这样我们使用时就不用管有没有存放过这个实例,反正没有的话get方法也会帮我们存放。
//获取实例方法 static public function get($key) { if (!isset(self::$objects[$key])) { self::$objects[$key] = new $key; } return self::$objects[$key]; }
当然使用这种方式的话,查看实例是否存在,就不能使用get方法了。因为调用get方法以后,不存在也会生成一个实例。
作者: 刀斧手何在
链接:http://www.imooc.com/article/17851
来源:慕课网
本文原创发布于慕课网 ,转载请注明出处,谢谢合作!
现在这个工厂方法只需要调用一次,以后再需要使用数据库连接对象,直接从全局的注册树上拿就行了
$db = Register::get("db");
至于这个工厂方法是么时候调用,可以在程序初始化的时候,我们的业务逻辑代码只需要在注册树上把这个对象读取出来即可。至此,三种最基本的设计模式就介绍完了。
最后
写这篇文章我花了4个多小时,不知道大家能不能看明白,反正写完后我自己是清晰多了。昨天晚上,我的第一篇文章Web服务器 - apache篇(一)居然被编辑推荐到简书的首页了,有点激动呀,第一次体验到了写文章的乐趣。高二时特别烦写作文(现在上高三但已经退学),每周两节作文课,我不想写,最后糊弄完交上作业,导致我写作水平特别差。前一段时间我就后悔呀,后悔不好好写作文,现在写一小段文字都要写半天,然后改好久。对于程序员来说写博客真的很重要,好久之前我就有写博客的想法,但就因为半天写不出几个字,迟迟没有开始写。
这三种设计模式比
作者: qq_自学成才是王道_0
链接:http://www.imooc.com/article/6223
来源:慕课网
ps 请先看
PHP设计模式(三)-建造者模式(Builder Pattern)
(一)单例模式,工厂模式,建造者模式,原型模式都属于创建型模式。使用创建型模式的目的,就是为了创建一个对象。
(二)创建型模式的优点,在于如何把复杂的创建过程封装起来,如何降低系统的内销。
(三)我认为创建型模式的一个总要的思想其实就是封装,利用封装,把直接获得一个对象改为通过一个接口获得一个对象。这样最明显的优点,在于我们可以把一些复杂的操作也封装到接口里去,我们使用时直接调这个接口就可以了。具体的实现,我们在主线程序中就不再考虑。这样使得我们的代码看上去更少,更简洁。
(四)单例模式,我们把对象的生成从new改为通过一个静态方法,通过静态方法的控制,使得我们总是返回同一个实例给调用者,确保了系统只有一个实例
(五)工厂模式,也是一样,生成对象改为接口,还可以通过传参实例化不同的类。如果我们通过直接new的话,那么我们在主线代码中少不了要写if condition new 一个加法类,else new一个减法类。封装了之后,我们通过接口传参,还能利用多态的特性去替代if else语句。
而且我们遵循了单一原则,让类的功能单一。我们如果需要一个新功能,只需添加一个类,不用修改其他类的功能。这样使得代码的扩展性更好了。
(六)建造者模式,我们把初始化的工作和顺序,封装给了一个建造者和指挥者。如果,我们下次要建造的类属性,或是顺序不同。我们只需新建对应的建造者类或添加对应的指挥者方法,不必再去修改原代码。而且我们也省去了,这new对象后,还要写$attribut=array();这种一大串数组,然后调好几个方法去初始化的工作。
(七)原型模式,通过先创建一个原型对象,然后直接克隆,省去了new大对象带来的开销浪费。当然我们同样可以通过,封装clone这个动作。使得我们在clone的同时还可以做一些其他的准备工作。
享元模式 (Flyweight Pattern): 池技术的重要实现方式, 运用共享技术有效的支持大量的细粒度对象,用于减少创建对象的数量,以减少内存占用和提高性能。
(一)为什么需要享元模式
1,在有大量对象时,有可能会造成内存溢出,我们把其中共同的部分抽象出来,如果有相同的业务请求,直接返回在内存中已有的对象,避免重新创建。
2,系统有大量相似对象。
3,需要缓冲池的场景。
(二)享元模式UML图
(三)简单实例
这里引用《设计模式之禅》的例子,就是如果有一个报考信息系统,每个考生进来就实例化一个对象,传入他要报考的考点。由于考生分为30个考点,所以其实我们没有必要不停的实例化对象并释放,我们完全可以做一个缓冲池,若缓冲池中没有与这个考生相同考点的对象,则实例化一个,使用完不需要释放。下一个相同考点的考生进来后,只需复用这个对象。
<?php
//抽象享元对象
abstract class Flyweight{
//考点
public $address;
//享元角色必修设置考点
public function __construct($address){
$this->address = $address;
}
}
//具体享元角色 考生类
class ConcreteFlyweight extends Flyweight{
//报考动作
public function register(){
echo "我的报考点是:{$this->address}".PHP_EOL;
}
//退出
public function quit(){
unset($this);
}
}
//享元工厂 缓冲池
class FlyweightFactor{
static private $students = array();
static public function getStudent($address){
$students =self::$students;
//判断该键值是否存在
if(array_key_exists($address,$students)){
echo "缓冲池有考点为{$address},从池中直接取".PHP_EOL;
}else{
echo "缓冲池没有,创建了考点为{$address}的对象并放到池中".PHP_EOL;
self::$students[$address] = new ConcreteFlyweight($address);
}
return self::$students[$address];
}
}
//实例化学生对象
$student_1 = FlyweightFactor::getStudent('广州');
//报考
$student_1 ->register();
// 退出
$student_1->quit();
//第二个学生进来
$student_2 = FlyweightFactor::getStudent('东莞');
//报考
$student_2 ->register();
// 退出
$student_2->quit();
//第三个学生进来
$student_3 = FlyweightFactor::getStudent('广州');
//报考
$student_3 ->register();
// 退出
$student_3->quit();
这里我们可以看到,当第三个学生进来时,由于和第一个学生的报考地点一致,所以只需从缓冲池取出。
ps:虽然ConcreteFlyweight的quit方法unset了$this,但是由于在FlyweightFactor中的students还存放着这个对象,所以unset只释放了变量$student_1,并没有完全删除这个对象,这就是student_3进来还能从缓冲池取得对象的原因
享元模式在PHP中可能比较少遇,但在Java中常常有这种情况。就是代码产生了大量的对象,虽然使用完有释放,但是由于垃圾回收需要一定时间,导致内存被耗尽。PHP经常应用web编程,脚本执行时间很短(30秒)。所以很少遇见这种情况,甚至我们使用完变量,连unset()函数都不用调用,就等着脚步执行结束后,自动释放。但是如果你试过在cli模式下运行PHP脚本,做一些socket通信,发送邮件等长耗时或是多连接任务时,就难免会遇到这种情况
代理模式(Proxy Pattern):构建了透明置于两个不同对象之内的一个对象,从而能够截取或代理这两个对象间的通信或访问。
(一)为什么需要代理模式
1,远程代理
,也就是为了一个对象在不同地址空间提供局部代表。隐藏一个对象存在于不同地址空间的事实。
2,虚拟代理
,根据需要来创建开销很大的对象,通过它来存放实例化需要很长时间的真实对象。
3,安全代理
,用来控制真实对象的访问对象。
4,智能指引
,当调用真实对象的时候,代理处理一些事情。
(二)代理模式UML图
(三)简单实例
案例一
:你想买一张学友哥的新唱片,以前你都是在县城CD店里买的。现在CD行业不景气,没得卖了。你只能找人去香港帮你代购一张。
<?php
//代理抽象接口
interface shop{
public function buy($title);
}
//原来的CD商店,被代理对象
class CDshop implements shop{
public function buy($title){
echo "购买成功,这是你的《{$title}》唱片".PHP_EOL;
}
}
//CD代理
class Proxy implements shop{
public function buy($title){
$this->go();
$CDshop = new CDshop;
$CDshop->buy($title);
}
public function go(){
echo "跑去香港代购".PHP_EOL;
}
}
//你93年买了张 吻别
$CDshop = new CDshop;
$CDshop->buy("吻别");
//14年你想买张 醒着做梦 找不到CD商店了,和做梦似的,不得不找了个代理去香港帮你代购。
$proxy = new Proxy;
$proxy->buy("醒着做梦");
案例二
:通过代理实现MySQL的读写分离,如果是读操作,就连接127.0.0.1的数据库,写操作就读取127.0.0.2的数据库
<?php
class Proxy
{
protected $reader;
protected $wirter;
public function __construct(){
$this->reader = new PDO('mysql:host=127.0.0.1;port=3306;dbname=CD;','root','password');
$this->writer = new PDO('mysql:host=127.0.0.2;port=3306;dbname=CD;','root','password');
}
public function query($sql)
{
if (substr($sql, 0, 6) == 'select')
{
echo "读操作: ".PHP_EOL;
return $this->reader->query($sql);
}
else
{
echo "写操作:".PHP_EOL;
return $this->writer->query($sql);
}
}
}
//数据库代理
$proxy = new Proxy;
//读操作
$proxy->query("select * from table");
//写操作
$proxy->query("INSERT INTO table SET title = 'hello' where id = 1");
//当然对于数据库来说,这里应该使用单例模式的方法来存放$reader和$writer,但我只是举个例子,不想把单例加进来把代码搞复杂。
//但是如果你要实现这样的一个数据库代理,我觉得还是有必要用上单例模式的知识
一句话来说,代理模式,就是在访问对象时通过一个代理对象去访问你想访问的对象。而在代理对象中,我们可以实现对访问对象的截断或权限控制等操作。
空对象模式(Null Object Pattern):用一个空对象取代 NULL,减少对实例的检查。这样的空对象可以在数据不可用的时候提供默认的行为
(一)为什么需要空对象模式
解决在需要一个对象时返回一个null值,使其调用函数出错的情况
(二)空对象模式UML图
上图是Java的空对象模式UML图,网上很多PHP设计模式的代码实现都是照着上面这个UML图
实际上PHP在空对象模式的实现上比Java更加简单,优雅。因为PHP有美妙的语法糖,魔术方法__call方法。
我们只要实现空对象的__call方法就可以实现空对象模式,并不需要使用nullobject去继承对应的抽象object
(三)简单实例
假设现在我们有这么一段代码
<?php
//测试类
class Person{
public function code(){
echo 'code makes me happy'.PHP_EOL;
}
}
//定义一个生成对象函数,只有PHPer才允许生成对象
function getPerson($name){
if($name=='PHPer'){
return new Person;
}
}
$phper = getPerson('PHPer');
$phper->code();
是的,现在这段代码会输出code makes me happy
。如果有时候这个函数是别人调用的,它并没传入合适的参数呢?
$phper = getPerson('Javaer');
$phper->code();
这个时候就会报错了error : Call to a member function code() on null
。是的$phper现在是一个null值,所以调用code方法就会报错
这种情况很常见,系统并没有返回一个我们期待的对象,而是返回了一个null值。所以多数情况下,我们的代码都要这样写
$phper = getPerson('Javaer');
if(!is_null($phper)){
$phper->code();
}
或者是
if(is_object($phper)){
$phper->code();
}
这样让太多的if判断不可避免地存在于我们的代码中
如果我们使用NullObject模式的话,我们就可以让函数没有返回值时返回一个nullobject对象。而不是一个null值(没有return 默认null值)
//测试类
class Person{
public function code(){
echo 'code makes me happy'.PHP_EOL;
}
}
//空对象模式
class NullObject{}
//定义一个生成对象函数,只有PHPer才允许生成对象
function getPerson($name){
if($name=='PHPer'){
return new Person;
}else{
return new NullObject;
}
}
$phper = getPerson('PHer');
$phper->code();
这个时候就不会再报一个null调用函数的错误了,但是会报一个call to undefined method的错误
,这是由于NullObject对象没有code方法。这个时候我们只需实现魔术方法__call
,就不会报错了。
//空对象模式
class NullObject{
public function __call($method,$arg){
echo 'this is NullObject';
}
}
我们可以通过返回一个NullObject对象来取代返回null,这样就不用在调用方法时判断是否为null,而且只要你实现了call方法,不管真正的对象它原来是调用那个方法的,NullObject都可以去调用而且不报错(`实际是隐式调用了魔术方法call`)。当然,如果你原本的逻辑是返回对象是null的话什么都不做,那么你可以让__call()什么都不做。或者你也可以让它抛出一个异常。