抽象/接口/静态继承/命名空间与实战
类的自动加载的实现原理
// 类的自动加载
// 查看当前脚本所在的目录
//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));
// 以后, 只要数据库发生了变化, 我们只需要改一下连接接口参数就可以, 项目代码不必做任何改动
来源:https://blog.csdn.net/weixin_44396392/article/details/100972064