设计模式及面向对象高级特性

325 阅读11分钟

今日目标

  • 能够掌握面向对象的高级特性

  • 能够具备设计纯面向对象框架和系统的能力

  • 能够遵循PSR-0规范开发一个基础框架

  • 能够掌握单例模式

  • 能够掌握工厂模式

  • 能够掌握注册树模式

一、什么是设计模式?

1.概念

设计模式(英语 design pattern)是对面向对象设计中反复出现的问题的解决方案。

2.举例

如果我们把面向对象编程当成一本武功秘籍,设计模式就是其中的每一招每一式,如果我们可以把设计模式式活学活用,那在面向对象编程方面那一定是一个非常厉害的高手了!

二、开发环境准备

一、编辑器的选址

1.下载PHPStorm
2.选择编程字体
  • 必须选择等宽字体
  • 常见的等宽变成自提包括Courier Nes,Consolas
  • 个人推荐大家使用Soure Code Pro,是由Adobe公司专为程序员设计,免费开源

默认字体展示出来的效果:

设置等宽字体:

设置玩字体展示出来的效果:

三、命名空间与类自动载入

一、 命名空间的介绍

概念

namespace即“命名空间”,也称“名称空间” 。各种语言使用的一种代码组织的形式 通过名称空间来分类,区别不同的代码功能

基本介绍

命名空间是用来组织和重用代码的。如同名字一样的意思,NameSpace(名字空间),之所以出来这样一个东西,是因为人类可用的单词数太少,并且不同的人写的程序不可能所有的变量都没有重名现象,对于库来说,这个问题尤其严重,如果两个人写的库文件中出现同名的变量或函数(不可避免),使用起来就有问题了。为了解决这个问题,引入了名字空间这个概念,通过使用 namespace xxx;你所使用的库函数或变量就是在该名字空间中定义的,这样一来就不会引起不必要的冲突了。

2.命名空间的使用

==a)不使用命名空间,案例演示==

运行报错(无法重新声明方法)

==b)使用命名空间案例演示==

正常运行(输出当前的文件名称)

3.Phpstorm在控制台中输出运行结果

参考链接:blog.csdn.net/weixin_3718…

1、先打开菜单Run->Edit Configurations选项

2、然后在打开的窗口中点击左上角的+号

3、在打开的菜单中选择PHP Script

4、在打开的界面中找到Configuration->File

5、选择你要在控制台运行的文件

6、填入工作目录的路径(可填可不填),完成配置后点击右下角的OK

7、回到主界面,点击菜单Run->Run

二、类自动载入

1.概念

在编写面向对象(OOP) 程序时,很多开发者为每个类新建一个 PHP 文件。 这会带来一个烦恼:每个脚本的开头,都需要包含(include)一个长长的列表(每个类都有个文件)。

spl_autoload_register() 函数可以注册任意数量的自动加载器,当使用尚未被定义的类(class)和接口(interface)时自动去加载。通过注册自动加载器,脚本引擎在 PHP 出错失败前有了最后一个机会加载所需的类。

==Note==

尽管 __autoload() 函数也能自动加载类和接口,但更建议使用 spl_autoload_register() 函数。 spl_autoload_register() 提供了一种更加灵活的方式来实现类的自动加载(同一个应用中,可以支持任意数量的加载器,比如第三方库中的)。因此,不再建议使用 __autoload() 函数,在以后的版本中它可能被弃用。

参考链接:www.php.net/manual/zh/f…

三、开发一个PRS-0的基础框架

1.PRS-0规范
  • 命名空间必须与绝对路径一致
  • 类名首字母必须大写
  • 除入口文件外,其他".php"必须只有一个类
2.开发符合PSR-0规范的基础框架
  • 全部使用命名空间
  • 所有PHP文件必须自动载入,不能有include/require
3.进行开发框架

进行带领同学们共同封装一个基础框架

进行测试框架

入口文件自动加载所有的类文件

五、PHP面向对象高级特性

一、SPL库的使用(PHP标准库)

参考链接:www.php.net/spl

==SPL库的使用(PHP标准库)==

  • SplStack、SplQueue、SplHeap、SplFixedArray等数据结构
  • ArrayItertor、AppendIterator、Countable、ArrayObject
  • SPL提供的函数
1.栈操作

概念:SplStack类通过使用一个双向链表来提供栈的主要功能。

特性:先进后出

$stack = new SplStack();
$stack->push("data1\n");
$stack->push("data2\n");

echo $stack->pop();
echo $stack->pop();
2.队列操作

概念:SplQueue 类通过使用一个双向链表来提供队列的主要功能。

特性:先进先出

$queue = new SplQueue();
$queue->enqueue("data1\n");
$queue->enqueue("data2\n");

echo $queue->dequeue();
echo $queue->dequeue();
3.最小堆

概念:SplMinHeap类提供堆的主要功能,将最小值保持在顶部。

$heap = new SplMinHeap();
$heap->insert("data1\n");
$heap->insert("data2\n");

echo $heap->extract();
echo $heap->extract();
4.最小长度的数组 固定尺寸数组

概念:SplFixedArray类提供了数组的主要功能。SplFixedArray与普通PHP数组之间的主要区别在于,必须手动调整SplFixedArray的大小,并且只允许范围内的整数作为索引。优点是它比标准阵列使用更少的内存。

$array = new SplFixedArray(10);
$array[0] = 123;
$array[9] = 1234;
var_dump($array);

二、PHP链式操作的实现

1.如何实现数据库链式操作

==$db->where()->limit()->order();==

2.实现代码

文件:Vendor/Database.php

<?php


namespace Vendor;


class Database
{
   
    function where($where)
    {
        return $this; //链式操作的核心就是返回当前对象
    }

    function order($order)
    {
        return $this;
    }

    function limit($limit)
    {
        return $this;
    }
}

文件:index.php

//不是链式操作
//$db = new Vendor\Database();
//$db->where("id=1");
//$db->where("name=1");
//$db->where("id desc");
//$db->limit(10);
//链式操作
$db->where("id=1")->where("name=2")->order("id desc")->limit(10);

三、PHP魔术方法的使用

1.常见的魔术方法
  • __get/__set
  • __call/__callStatic
  • __toString
  • __invoke
2.魔术方法的实现

==__get/__set==

  • __set 当对对象不存在的属性进行赋值时会进行调用该方法
  • __get 当读取一个对象不存在的属性时会调用该方法

index.php文件

//$obj = new Vendor\Obj();
//调用对象不存在的属性时
//$obj->title = 'hello';
//echo $obj->title;

Vendor/Obj.php文件

    protected $array = [];

    //当对对象不存在的属性进行赋值时会进行调用该方法
    function __set($key, $value)
    {
        var_dump(__METHOD__);
        $this->array[$key] = $value;
    }

    //当读取一个对象不存在的属性时会调用该方法
    function __get($key)
    {
        var_dump(__METHOD__);
        return $this->array[$key];
    }

==__call/__callStatic==

  • __call 当调用对象中不存在的方法时自动调用
  • __callStatic 当调用对象中不存在的静态方法时自动调用 index.php文件
//调用对象不存在的方法时
//echo $obj->test('hello',123);

//调用对象不存在的静态方法时
//echo $obj::test('hello',123);

Vendor/Obj.php文件

    //当调用一个不存在的方法是会自动调用
    function __call($name, $arguments)
    {
        // TODO: Implement __call() method.
        var_dump($name,$arguments);
        return "magic function\n";
    }

    //当调用不存在的静态方法时会自动调用
    public static function __callStatic($name, $arguments)
    {
        // TODO: Implement __callStatic() method.
        var_dump($name,$arguments);
        return "magic static function\n";
    }

==__toString==

当把对象当成字符串使用时,会自动调用该方法

index.php文件

//将对象转成成字符串,会自动调用该方法
//echo $obj;

Vendor/Obj.php文件

    //将对象转成成字符串,会自动回调该方法
    public function __toString()
    {
        // TODO: Implement __toString() method.
        //返回一个字符串
        return __CLASS__;
    }

==__invoke==

index.php文件

echo $obj('test1');

Vendor/Obj.php文件

    //把对象当成一个函数去用,会调用该方法
    public function __invoke($params)
    {
        var_dump($params);
        // TODO: Implement __invoke() method.
        return 'invoke';
    }

四、三种基础设计模式

一、基础设计模式简介

  • 工厂模式(工厂方法或者类生成对象,而不是代码中直接new)
  • 单例模式(使某个类的对象仅允许创建一个)
  • 注册模式(全局共享和交换对象)

二、[php]工厂模式

1. 概念

工厂模式是我们最常用的实例化对象模式,是用工厂方法代替new操作的一种模式。

2.那么何为工厂模式?

 从名字来看,似乎看不出什么端倪。工厂模式,和生产有关?还是和生产流程有关?难道还和工厂领导有关?和领导秘书有关?秘书... 好了不卖关子了,所谓工厂模式还真和生产有关。生产什么呢?生产出来的是一个实例对象。通过什么设备生产?通过一个工厂类生产。怎么生产呢?工厂类调用自身静态方法来生产对象实例。

 工厂模式有一个关键的构造,根据一般原则命名为Factory的静态方法,然而这只是一种原则,虽然工厂方法可以任意命名这个静态还可以接受任意数据的参数,必须返回一个对象。

3. 为什么要用工厂模式?

很多没接触过工厂模式的人会不禁问,为啥我要费那么大的劲儿去构造工厂类去创建对象呢?不去套用那些易维护,可扩展之类的话,我们可以考虑这样 一个简单的问题。如果项目中,我们通过一个类创建对象。在快完成或者已经完成,要扩展功能的时候,发现原来的类类名不是很合适或者发现类需要添加构造函数 参数才能实现功能扩展。我靠!我都通过这个类创建了一大堆对象实例了啊,难道我还要一个一个去改不成?我们现在才感受到了“高内聚低耦合”的博大精深。没 问题,工厂方法可以解决这个问题。

==如果类名发生变化,或者方法的参数发生变化,需要修改所有引入该类的文件,不易于维护,紧耦合。我们要解决这个问题,所以要使用工厂方法。==

==工厂模式更多的是解决后期拓展(大部分是文件命名修改后)的问题。==

3.工厂模式如何实现?

相对于单例模式,上面我们提供了足够的信息,工厂类,工厂类里面的静态方法。静态方法里面new一下需要创建的对象实例就搞定了。当然至于考虑 上面的第二个问题,根据工厂类静态方法的参数,我们简单做个判断就好了。管你用if..else..还是switch..case..,能快速高效完成判 断该创建哪个类的工作就好了。最后,一定要记得,工厂类静态方法返回一个对象。不是两个,更不是三个。

index.php文件

//工厂模式
//$db = new Vendor\Database();
//$db = Vendor\Factory::createDatabase();
//var_dump($db);

Vendor/Factory.php文件

<?php


namespace Vendor;


class Factory
{
    static function createDatabase()
    {
        //统一进行实例化数据库连接
        $db = new Database();
        return $db;
    }
}

==总结:工厂模式是其设计模式惯用的基础模式,其它高级的设计模式都是依赖于工厂 模式==

三、[php]单例模式

1.概念:

作为对象的创建模式,单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统全局地提供这个实例。它不会创建实例副本,而是会向单例类内部存储的实例返回一个引用。

2.为什么要使用单例模式:

php使用单例模式的原因:在使用面向对象的方式开发时,使用单例模式可以避免过多的new操作消耗大量的资源。单例模式可以通过一个类来全局控制某些配置信息。

==单例模式更多的是解决内存开销(new 类),同样也是为了代码美观吧==

3.案例

php的应用主要在于数据库应用, 所以一个应用中会存在大量的数据库操作,在使用面向对象的方式开发时,如果使用单例模式,则可以避免大量的new操作消耗的资源。如果系统中需要有一个类来全局控制某些配置信息,那么使用单例模式可以很方便的实现。

4.实现的方法

Vendor/Database.php文件

<?php


namespace Vendor;


class Database
{
    private static $db;
    private function __construct()
    {
    }
    
    //获取实例
    static function getInstance()
    {
        if (self::$db){
            return self::$db;
        }else{
            self::$db = new self();
            return self::$db;
        }

    }

    function where($where)
    {
        return $this;
    }

    function order($order)
    {
        return $this;
    }

    function limit($limit)
    {
        return $this;
    }
}

==总结:单例模式解决了资源的浪费,因为我们在程序的执行过程中,只需要创建一个数据库的连接即可,所以说我们的数据库一定要使用单例模式来实现。单例模式,无论调用的多少次,只会创建一次对象。==

四、[php]注册树模式

1.什么是注册树模式?

注册树模式当然也叫注册模式,注册器模式。之所以我在这里矫情一下它的名称,是因为我感觉注册树这个名称更容易让人理解。像前两篇一样,我们这 篇依旧是从名字入手。注册树模式通过将对象实例注册到一棵全局的对象树上,需要的时候从对象树上采摘的模式设计方法。 这让我想起了小时候买糖葫芦,卖糖葫芦的将糖葫芦插在一个大的杆子上,人们买的时候就取下来。不同的是,注册树模式摘下来还会有,能摘很多次,糖葫芦摘一 次就没了。。。

2.为什么要使用注册树模式?

单例模式解决的是如何在整个项目中创建唯一对象实例的问题,工厂模式解决的是如何不通过new建立实例对象的方法。 那么注册树模式想解决什么问题呢? 在考虑这个问题前,我们还是有必要考虑下前两种模式目前面临的局限。 首先,单例模式创建唯一对象的过程本身还有一种判断,即判断对象是否存在。存在则返回对象,不存在则创建对象并返回。

每次创建实例对象都要存在这么一层判断。

工厂模式更多考虑的是扩展维护的问题。

总的来说,单例模式和工厂模式可以产生更加合理的对象。怎么方便调用这些对象呢?而且在项目内如此建立的对象好像散兵游勇一样,不便统筹管理安排啊。因 而,注册树模式应运而生。不管你是通过单例模式还是工厂模式还是二者结合生成的对象,都统统给我“插到”注册树上。我用某个对象的时候,直接从注册树上取 一下就好。这和我们使用全局变量一样的方便实用。 而且注册树模式还为其他模式提供了一种非常好的想法。

3.如何实现注册树?

 通过上述的描述,我们似乎很容易就找到了解决方法。首先我们需要一个作为注册树的类,这毋庸置疑。所有的对象“插入”到注册树上。这个注册树应 该由一个静态变量来充当。而且这个注册树应该是一个二维数组。这个类应该有一个插入对象实例的方法(set()),当让相对应的就应该有一个撤销对象实例 的方法(_unset())。当然最重要的是还需要有一个读取对象的方法(get())。拥有这些,我们就可以愉快地完成注册树模式啦~~~

Vendor/Register.php文件

<?php


namespace Vendor;


class Register
{
    protected static $objects;

     /**表示将一个对象注册到全局的注册树上
     * @param $alias 映射的名字
     * @param $object 对象
     */
    static function set($alias,$object)
    {
        //当调用set方法是将我们的对象映射到注册树上
        self::$objects[$alias] = $object;
    }

    //调用_unset方法是,把它从树上移出
    function _unset($alias)
    {
        unset(self::$objects[$alias]);
    }


    static function get($name)
    {
       return self::$objects[$name];
    }
}

==总结:什么时候注册到树上,什么时候卸载,可以由我们的环境初始化环境工作中把它做了,在我们业务逻辑代码中,只需要把这个对象读取出来即可。==