抽象/接口/静态继承/命名空间与实战(一)

落爺英雄遲暮 提交于 2019-11-29 23:43:00

抽象/接口/静态继承/命名空间与实战

类的自动加载的实现原理

// 类的自动加载

// 查看当前脚本所在的目录
//echo __DIR__,  '<br>';


//include __DIR__ . '/inc/Test1.php';
//include __DIR__ . '/inc/Test2.php';
//include __DIR__ . '/inc/Test3.php';

// 如果当前脚本使用了几十上百这样的类, 上面的方式就很不人性
// 使用下面的自动加载机制, 会根据客户端调用的类, 自动进行加载,效率高, 不出错

// php标准函数库中提供了一个自动加载文件的注册函数,可以实现这个功能
// 这个函数,在当前脚本引用一个未加载的文件时, 会自动调用它的回调方法来加载这个文件
spl_autoload_register(function ($class){
//    include __DIR__ . '/inc/Test1.php';
//    将include中的类名Test1用变量替换掉,这样就实现了最简单的自动加载
    // 后面我们会使用命名空间来完善这个函数,目前大家先理解到这里即可
    include __DIR__ . '/inc/'.$class.'.php';
});

$test1 = new Test1();
echo $test1->get(), '<br>';

$test1 = new Test2();
echo $test1->get(), '<br>';

$test1 = new Test3();
echo $test1->get(), '<br>';

抽象类与应用

// 在实际开发过程中, 通常并不会直接使用一个父类/超类,而是在父类中定义一些方法声明
// 并且确信这个方法肯定是会被子类重写的, 父类中没必要实现,只要给一个方法编写规范即可
// 这个规范包括方法的名称, 参数列表等,具体实现就完全交给子类去完成了
// 相当于公司的部门主管, 接受到老板的任务, 只把属于自己的部分做了, 其它部分, 设置一个标准交给下属去完成
// 这个老板就是接口,我们一会会讲到, 这个部门经理,我们用抽象类实现, 而下属就是子类啦,最终干活的就是子类

// 类中只要有一个成员设置为抽象,该类就必须设置抽象类
// 一个类一旦被设置为抽象类,就具备了二个特点:
// 1. 不能实例化; 2. 类中抽象方法,在子类中必须实现
// 注意:
// 1.属性设置为抽象无意义, 抽象仅针对方法
// 2.子类的成员可见性必须等于或高于所继承的抽象类成员可见性,例如抽象类是proteced,子类可以是protected/pulic
// 子类重写的抽象方法可见性,究竟用protected/pulic, 要看这个子类是不是最终被客户端访问,如果是就public,如果不是protected

abstract class Person2
{
    protected $name;

    protected function __construct($name='peter zhu')
    {
        $this->name = $name;
    }

    // 该方法不需要重写, 可以通过子类对象访问,应该设置为public
    public function getName()
    {
        return $this->name;
    }

    // 修改属性方法,设置为抽象方法,交给子类实现
    abstract protected function setName($value);
}

class Stu extends Person2
{
    // 注意: 构造方法不会自动继承, 必须手动重写
    public function __construct(string $name = 'peter zhu')
    {
        parent::__construct($name);
    }

    public function setName($value)
    {
        $this->name = $value;
    }
}

$stu = new Stu('猪哥');

echo 'php中文网创始人: ' . $stu->getName() . '<br>';

// 调用子类的重写的抽象方法setName(),来设置属性
$stu->setName('灭绝师太');
echo 'php中文网前端讲师: ' . $stu->getName() . '<br>';

接口原理与实现

* `interface`: 指定某个类必须实现的方法,但不需要定义方法的具体实现过程
* 接口中仅允许出现: 方法与类常量
* 接口的方法可见性必须是: public
* 接口的方法体必须是空的
* 接口是类的代码模板, 可以像类一样有父子继承关系,例如父接口, 子接口
* `implements`: 类实现接口的关键字, 读音: ['ɪmplɪmɛnts,应波罗曼次]
* 如果仅是部分实现接口中的方法, 请用一个抽象类来实现它
// 接口

interface iVehicle
{
    // 驱动方式: 汽车, 新能源
    public function setFuel($fuel);

    // 用途
    public function setPurpose($purpose);
}

// Auto 类 实现了接口:  iVehicle
class Car implements iVehicle
{
    public $fuel;
    public $purpose;

    // 构造方法
    public function __construct($fuel='汽油', $purpose='家用')
    {
        $this->fuel = $fuel;
        $this->purpose = $purpose;
    }

    // 必须实现的接口方法
    public function setFuel($fuel)
    {
        $this->fuel = $fuel;
    }

    // 必须实现的接口方法
    public function setPurpose($purpose)
    {
        $this->purpose = $purpose;
    }

    // 类中自定义的对象方法
    public function getInfo()
    {
        return $this->fuel . $this->purpose . '车 <br>';
    }
}

// 客户端代码

$car = new Car();
echo $car->getInfo();

$car->setFuel('新能源');
$car->setPurpose('公交');
echo $car->getInfo();

echo '<hr>';


/**************************************************************/

// 如果暂时只能实现接口中的部分方法, 可以用一个抽象来实现这个接口
abstract class Auto implements iVehicle
{
    public $fuel;

    // 只实现接口中的setFuel()方法, 另一个方法并未实现
    public function setFuel($fuel)
    {
        $this->fuel = $fuel;
    }
}

// 再创建一个类,来继承扩展这个抽象类  Auto
class Car1 extends Auto
{
    public $purpose;

    // 构造方法
    public function __construct($fuel='汽油', $purpose='家用')
    {
        $this->fuel = $fuel;
        $this->purpose = $purpose;
    }

    // 这个方法原来在接口中声明的,在它继承的抽象类中并没有声明
    public function setPurpose($purpose)
    {
        $this->purpose = $purpose;
    }

    // 自定义的方法
    public function getInfo()
    {
        return $this->fuel . $this->purpose . '车 <br>';
    }
}

// 客户端代码

$car1 = new Car1();
echo $car1->getInfo();

$car1->setFuel('天然气');
$car1->setPurpose('家用');
echo $car1->getInfo();

/*
 * 总结:
 * 如果不能将接口中方法全部实现就用抽象类来实现它
 * 否则,就必须全部把接口中方法全部实现
 */

// 面向接口编程, 是现代面向对象开发的重要基础, 下面以一个有实用价值的案例进行讲解

接口实战:数据库的curd操作

// 接口开发实战案例

// 定义一个接口,实现数据库常用操作:增删改查
interface iCurd
{
    // 增加数据
    public function create($data);

    // 读取数据
    public function read();

    // 更新数据
    public function update($data, $where);

    // 删除数据
    public function delete($where);
}

// 创建Db类, 实现iCurd接口,完成基本的数据库操作
class Db implements iCurd
{

    //数据库的连接对象
    protected $pdo = null;

    // 数据表名
    protected $table;

    // 构造方法: 连接数据库,并设置默认数据表名称
    public function __construct($dsn, $user, $password, $table='staff')
    {
        $this->pdo = new PDO($dsn, $user, $password);
        $this->table = $table;
    }

    // 读取
    public function read($fields='*', $where='', $limit='0, 5')
    {
        // 设置查询条件
        $where = empty($where) ? '' : ' WHERE ' . $where;
        // 设置显示数量
        $limit = ' LIMIT ' . $limit;

        // 预处理查询操作
        $sql = 'SELECT '.$fields.' FROM '.$this->table.$where.$limit;
        $stmt = $this->pdo->prepare($sql);
        $stmt->execute();

        // 返回二维数组表示的查询结果集
        return $stmt->fetchAll(PDO::FETCH_ASSOC);
    }

    // 新增, 参数是数组: 新记录的键值对
    public function create($data)
    {
        // 字段列表
        $fields = ' (name,age,sex,position,mobile,hiredate)';
        // 值列表
        $values = '(:name,:age,:sex,:position,:mobile,:hiredate)';
        // 创建SQL语句
        $sql = 'INSERT INTO '.$this->table.$fields.' VALUES '.$values;

        // 预处理执行新增操作
        $stmt = $this->pdo->prepare($sql);
        $stmt->execute($data);

        // 返回新增数量, 新增记录的ID组成的数组
        return [
            'count'=>$stmt->rowCount(),
            'id'=>$this->pdo->lastInsertId()
        ];
    }

    // 更新, 为了数据安全, 不允许无条件更新
    public function update($data, $where)
    {
        // 难点在于SET 参数的处理上,利用传入的$data数组,进行拆装

        // 获取数组的键名组成的数组
        $keyArr = array_keys($data);
        $set = '';
        // 遍历键名表示的字段列表,拼装预处理需要的sql语句,注意占符符的表示
        foreach ($keyArr as $value) {
            $set .= $value . ' = :' .$value. ', ';
        }
        // 去掉最后一个逗号, 注意每个逗号后有一个空格,去除时也要带上这个空格
        $set = rtrim($set,', ');

        // 预处理执行更新操作
        $sql = 'UPDATE '.$this->table.' SET '.$set .' WHERE ' .$where;
        $stmt = $this->pdo->prepare($sql);
        $stmt->execute($data);

        // 返回被更新的记录数量
        return $stmt->rowCount();
    }

    // 删除: 与更新一样, 这也是危险的写操作, 不允许无条件删除
    public function delete($where)
    {
        // 预处理执行删除操作
        $sql = 'DELETE FROM '.$this->table.' WHERE '.$where;
        $stmt = $this->pdo->prepare($sql);
        $stmt->execute();
        return $stmt->rowCount();
    }
}

// 客户端的测试代码
// 实例化Db类
$dsn = 'mysql:host=localhost;dbname=php';
$user = 'root';
$password = 'root';
$db = new Db($dsn, $user, $password);


// 遍历读取
foreach ($db->read() as $item) {
    print_r($item); echo '<br>';
}

echo '<hr>';

// 新增数据

$data = [
    'name'=>'郭靖',
    'age'=>30,
    'sex'=>1,
    'position'=>'金刀驸马',
    'mobile'=>'13666668888',
    'hiredate'=>time()
];
$res = $db->create($data);
echo '成功新增'.$res['count'].'条记录,最新记录的主键ID是: '.$res['id'];

echo '<hr>';


// 更新记录
$data = [
    'age' => 40,
    'position'=>'抗金英雄'
];
$where = 'id = 24';
echo '成功更新了: ' .$db->update($data, $where). ' 条记录';

echo '<hr>';

// 删除记录
$where = 'id = 24';
echo '成功更新了: ' .$db->delete($where). ' 条记录';

后期静态绑定的应用场景与实现原理

// 延迟静态绑定技术

/*
 * ::  范围解析符的使用场景
 * 1. 访问类方法与类常量
 * 2. 访问被重写的对象或类方法
 * 以上二种应用方式,大家想必不再陌生
 *
 */

// 演示使用范围解析符(::)访问被重写的方法
// 为了简化代码,直接引用php官网上的例子:
// http://cn2.php.net/manual/zh/language.oop5.late-static-bindings.php
class A {
    public static function who() {
        echo __CLASS__;
    }
    public static function test() {
//        self::who();

        // 那么如何在这种静态继承的上下文环境中, 静态调用类中方法的时候,正确识别调用者呢?
        // 可以将self 关键字改为: static ,
        // 注意: static 除了可以用在静态方法中, 也可以用在普通对象方法中
        static::who();
    }
}

class B extends A {
    public static function who() {
        echo __CLASS__;
    }
}

// 子类B,调用父类中的test()方法,期望返回的是被子类重写的test()方法,返回调用类: B
// 但实际上却返回的: A
B::test();
echo '<hr>';
// 总结: static关键字用来调用重写方法的时候,可以动态的绑定当前调用的类
// 这种绑定是在运行阶段发生, 而不是代码编写的词法分析阶段(静态的代码文本),所以要后期延迟静态绑定


// 这种延迟绑定,有什么卵用呢? 用处非常大,特别是在现代的PHP框架开中, 随处可见
// 下面举一个简单的小案例,来看一下它的使用场景:

/***********************************************/

// 以最简单的数据库连接,来演示延迟静态绑定的应用

class Connection
{
    public static function connect()
    {
        // self::调用是当前Connection类中的config方法,而并非在子类被重写的config方法
        // 因为用户在子类中重新定义了连接参数, 所以查询会失败
//        return self::config();

        // 使用static:: 根据调用者,动态的调用被重写的方法,这样就可以调用到被重写的方法了
        return static::config();
    }

    public static function config()
    {
        return  new PDO('mysql:dbname=php','root', '123456');
    }
}

class Link extends Connection
{
    public static function config()
    {
        return  new PDO('mysql:dbname=php','root', 'root');
    }
}

$pdo = Link::connect();
//var_dump($pdo instanceof PDO);

$staffs = $pdo->query('select * from staff limit 5');
foreach ($staffs as $staff) {
    print_r($staff); echo '<br>';
}

接口常量的作用:当做配置文件来用

// 接口常量的作用
namespace Test;

if (!interface_exists(__NAMESPACE__.'\iDbParam')) {
    interface iDbParam
    {
        const TYPE = 'mysql';
        const HOST = 'localhost';
        const USER_NAME = 'root';
        const PASSWORD = 'root';
        const DBNAME = 'php';
        public static function Connection ();
    }
}



class Connection implements namespace\iDbParam
{
    // 初始化连接参数
    private static $type = iDbParam::TYPE;
    private static $host = iDbParam::HOST;
    private static $userName = iDbParam::USER_NAME;
    private static $password = iDbParam::PASSWORD;
    private static $dbname = iDbParam::DBNAME;

    public static function Connection()
    {
        $dsn = self::$type.':host='.self::$host.';dbname='.self::$dbname;
        $user = self::$userName;
        $password = self::$password;
        $pdo = new \PDO($dsn,$user,$password);
        return $pdo;
    }
}

// 以后连接数据库只需要这个静态方法即可, 注意命名空间
$link = Connection::Connection();

//  执行一个查询进行测试
$stmt = $link->prepare('select * from staff limit 5');
$stmt->execute();
print_r($stmt->fetchAll(\PDO::FETCH_ASSOC));

// 以后, 只要数据库发生了变化, 我们只需要改一下连接接口参数就可以, 项目代码不必做任何改动
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!