面向对象编程的特性
面向对象编程具有封装、继承、多态三大特性,它们迎合了编程中注重代码重用性、灵活性和可扩展性的需要,奠定了面向对象在编程中的地位。
1) 继承
继承就是派生类(子类)自动继承一个或多个基类(父类)中的属性与方法,并可以重写或添加新的属性或方法。继承这个特性简化了对象和类的创建,增加了代码的重用性。
例如,已经定义了 A 类,接下来准备定义 B 类,而 B 类中有很多属性和方法与 A 类相同,那么就可以用 B 类继承 A 类,这样就不用再在 B 类中定义 A 类中已有的属性和方法,从而可以在很大程度上提高程序的开发效率。
继承分为单继承和多继承,PHP 目前只支持单继承,也就是说一个子类有且只有一个父类。
2) 封装
封装就是将一个类的使用和实现分开,只保留有限的接口(方法)与外部联系。对于用到该类的开发人员,只要知道这个类该如何使用即可,而不用去关心这个类是如何实现的。这样做可以让开发人员更好地把精力集中起来专注于别的事情,同时也避免了程序之间的相互依赖而带来的不便。
例如,在使用计算机时,我们并不需要将计算机拆开了解它每个部件的具体用处,只需要按下电源键就能将计算机启动,这就体现了封装的好处。
3) 多态
对象的状态是多变的。一个对象相对于同一个类的另一个对象来说,它们拥有的属性和方法虽然相同,但却可以有着不同的状态。另外,一个类可以派生出若干个子类,这些子类在保留了父对象的某些属性和方法的同时,也可以定义一些新的方法和属性,甚至于完全改写父类中的某些已有的方法。多态增强了软件的灵活性和重用性。
PHP abstract:抽象类和抽象方法
我们通常使用下面这些关键字来修饰类:
abstract:抽象类或方法,被修饰为抽象类之后,类将不能被实例化,但可以被继承。如果类中有至少一个方法被声明为抽象的,那么这个类也必须声明为抽象的。继承一个抽象类的时候,子类必须重新定义父类中的所有抽象方法,而且这些方法的访问控制必须和父类中一样。
final:使用 final 修饰的类不能被继承,而使用 final 修饰的方法不能在子类中重新定义。
注意:一个类可以包含有属于自己的常量、变量(在类中称为“成员属性”或者“属性”)以及函数(在类中称为“成员方法”或者“方法”)。
1、抽象方法
抽象方法是没有方法体的方法,所谓的没有方法体指的就是,在声明方法时候没有花括号{ }
以及其中的内容,而是直接在方法名后加上分号结束。另外,在声明抽象方法时要使用“abstract”关键字修饰。格式如下所示:
abstract 访问权限修饰符 function 方法名1(参数列表);abstract 访问权限修饰符 function 方法名2(参数列表);abstract public function show();
2、抽象类的定义
只要一个类里面有一个方法是抽象方法,那么这个类就必须定义为抽象类,抽象类也需要使用“abstract”关键字来修饰,抽象类中也可以包含不是抽象方法的成员方法和成员属性,但访问权限不能是私有的(使用 private 关键字修饰),因为抽象类中的方法需要被子类继承。
abstract关键字用于定义抽象类
在抽象方法前面添加abstract关键字可以标明这个方法是抽象方法不需要具体实现{}
抽象类中可以包含普通的方法,有方法的具体实现
继承抽象类的关键字是extends
继承抽象类的子类需要实现抽象类中定义的抽象方法
抽象类不能被实例化,当子类继承抽象类的时候,所有的抽象的方法都必须定义
// 抽象类:可以为子类定义一些公共接口作为子类重写的模板来使用// 抽象类可以有抽象方法,也可以有具体的方法 抽象类自身时不能被实例化的,需要被继承的子类来实例化对象abstract class Person{ public $name; protected $country; // 构造方法 function __construct($name="admin",$country="China"){ $this->name = $name; $this->country = $country; } // 抽象方法 没有方法体{} abstract public function eat(); abstract protected function say(); // 普通方法 public function run(){ echo "runing ……"; }}// 通过子类继承来实现抽象类中的抽象方法class Chinese extends Person{ public function say(){ echo $this->name.'是'.$this->country.'人,说'.$this->country.'话<br>'; } public function eat(){ echo "{$this->name}是{$this->country}人,使用筷子吃饭<br>"; }}$xiaom = new Chinese('小明','中国');$xiaom->say();$xiaom->eat();class American extends Person{ public function say(){ echo $this->name.'是'.$this->country.'人,说'.$this->country.'话<br>'; } public function eat(){ echo "{$this->name}是{$this->country}人,使用刀叉吃饭<br>"; }}$jkx = new American('杰克逊','美国');$jkx->say();$jkx->eat();
抽象类就像是一个“半成品”的类,在抽象类中包含没有被实现的抽象方法,所以抽象类是不能被实例化的,即创建不了对象,也就不能直接使用它。
3、抽象类的使用
可以将抽象类看作是为它的子类定义公共接口,将它的操作(可能是部分也可能是全部)交给子类去实现。就是将抽象类作为子类重载的模板使用的,定义抽象类就相当于定义了一种规范,这种规范要求子类去遵守。
当子类继承抽象类以后,就必须把抽象类中的抽象方法按照子类自己的需要去实现。子类必须把父类中的抽象方法全部都实现,否则子类中还存在抽象方法,所以还是抽象类,也就不能实例化为对象。
<?php abstract class Website{ public $name = 'php中文网<br>'; public $url = 'http://php.php/<br>'; abstract function title($title); abstract function output(); abstract function run(); } // 子类继承抽象类,来重写抽象方法 class SonDemo extends Website{ public function title($title){ echo $title; } public function output(){ echo $this -> name.$this -> url; } // 实在用不到的可以将其函数体留空,但必须要把抽象方法全部都实现 public function run(){ } } $obj = new SonDemo(); $obj -> title("抽象类和抽象方法"); $obj -> output();?>
PHP interface:接口
继承的特性简化了对象、类的创建,增加了代码的重用性。但 PHP 只支持单继承,也就是说每个类只能继承一个父类。为了解决这个问题,PHP 引入了接口。接口是一种特殊的抽象类,而抽象类又是一种特殊的类,所以可以将接口看作是一种特殊的类。
接口就是把不同类的共同行为进行定义,然后在不同的类里面实现不同的功能
interface定义接口,implements用于表示类实现某个接口
接口里面的方法没有具体的实现,无需写{}
实现了某个接口的类必须提供接口中定义的方法的具体实现
不能实例化接口,但是能够判断某个对象是否实现了某个接口。
instanceof关键字判断某个对象是否实现了某个接口
$object instanceof interface
接口可以继承接口(interface extends interface)
接口中定义的所有方法都必须是公有,这是接口的特性
1、接口的声明
如果抽象类中的所有方法都是抽象方法,我们就可以使用另外一种声明方式——“接口”技术。我们都知道类的声明是使用“class”关键字,而接口的声明则是使用“interface”关键字。声明接口的格式如下所示:
interface 接口名称{ // 常量成员 // 抽象方法}
接口中所有的方法都是抽象方法,而且不需要在方法前使用 abstract 关键字进行修饰。而且在接口中也不需要显示地使用 public 访问权限来进行修饰,因为默认权限就是 public 的,也只能是 public(公有的)。另外,接口中不能声明变量,只能使用 const 关键字声明为常量类型的成员属性。
接口和抽象类一样也不能实例化为对象,它是一种更严格的规范,也需要通过子类来实现。与抽象类不同的是,接口可以直接使用接口名称在接口外面获取常量成员的值
<?php interface Demo{ const NAME = 'php中文网'; const URL = 'http://php.cn/'; function fun1(); function fun2(); } echo Demo::NAME.'<br>'; echo Demo::URL.'<br>';?>
2、接口的应用
因为接口不能进行实例化操作,所以要使用接口中的成员,就必须借助子类。在子类中继承接口需要使用 implements 关键字,如果要实现多个接口的继承,那么每个接口之间使用逗号,
分隔。
在使用 implements 关键字继承接口的同时,还可以使用 extends 关键字来继承一个类。也就是说,可以在继承一个类的同时实现多个接口,但一定要先使用 extends 继承类再去使用 implements 实现多个接口。语法格式如下:
class 子类名 extends 父类名 implements 接口 1, 接口 2, ..., 接口 n { // 实现所有接口中的抽象方法}
举个栗子:
<?php interface Demo{ const NAME = 'php中文网'; const URL = 'http://php.cn/'; static function getName(); static function getUrl(); } class Website implements Demo{ static public function getName(){ echo self::NAME.'<br>'; } static public function getUrl(){ echo self::URL; } } // 通过接口可以直接访问常量 echo Demo::NAME."<br>"; echo Demo::URL."<br>"; // 调用Website类实现的静态方法 Website::getName(); Website::getUrl();?>
提示:既然要通过子类继承了接口中的方法,那么接口中的所有方法都必须在子类中实现,否则 PHP 将抛出如下所示的错误信息:
说了这么多,那么使用接口具体有什么作用呢?其实它的作用很简单,当有很多人一起开发一个项目时,每个人都可能会去调用别人写的一些类。这时有人就会问了,我怎么知道别人的某个功能的实现方法是怎么命名的呢?这个时候 PHP 接口就起到作用了。
简单来说,我们可以将接口看作一个类的模板或者类的规定。如果你属于这类,你就必须遵循这个类的规定,少一个都不行,但是具体怎么去做,那是你的事。也就是说我们可以定义一些接口,每个接口中都包含若干的抽象方法。在多人开发时,每个人都根据自己的需要来实现一部分接口,这样就可以避免我们在调用别人开发的方法时不知道方法名的尴尬了。
PHP static:后期静态绑定
PHP 增加了一个叫做后期静态绑定的功能,用于在继承范围内引用静态调用的类。
https://www.php.net/manual/zh/language.oop5.late-static-bindings.php
后期静态绑定的基本思想是继承的概念和’self’关键字的概念不遵循相同的规则。例如,父类在子类中调用的方法“ fun”不会像预期的那样使“自身”引用子代。
后期静态绑定的概念引入了一个新关键字“ static”,该关键字在使用时将功能绑定到运行时类或首次使用该功能的类。除此之外,任何静态函数或变量通常在运行时而不是在编译时执行。因此,如果需要将值动态分配给静态变量,则它会在运行时发生,这称为后期静态绑定。
// $this->对象引用class Test{ public function connect():string{ return "父类Test中的方法".__METHOD__; } public function show():string{ // 这里的$this->connect()指向当前实例对象的connect方法 return $this->connect(); }}class Pro extends Test{ public function connect():string{ return "子类Pro中的方法".__METHOD__; } /* 相当于继承到子类中 public function show():string{ // 这里的$this->connect()指向当前实例对象的connect方法 return $this->connect(); } */}// 在传统的继承关系中,非静态的方法$this可以动态的与当前调用对象进行绑定echo (new Test)->show()."<br>";echo (new Pro)->show()."<br>";/*父类Test中的方法Test::connect子类Pro中的方法Pro::connect*/
static 静态成员 类名称/self/static::引用
// self::类引用<?php // static 标志类中的静态成员 属于类的 不需要实例化得到对象去访问静态成员 有助于减少类的实例化所需要的开销 class student { public static $my_name = 'Joe'; public static function getName() { return "The name of the student is : " . self::$my_name; } public static function getAge() { //获取类的实例 self 始终永远和当前类进行绑定 //1. self 始终永远和当前类进行绑定 //2. self 在静态继承上下文中, 不能动态的识别或者设置静态成员的调用者 echo self::getName(); // self 关键字访问自身的静态成员 } } class Professor extends student { public static function getName() { return "The name of the student is : " . self::$my_name . " 并且年龄是24。"; } } student::getAge(); // The name of the student is : Joe echo "<br>"; // 始终调用的是父类中的方法 Professor::getAge(); // The name of the student is : Joe?>
<?php // static::类引用 class student { public static $my_name = 'Joe'; public static function getName() { return "The name of the student is : " . self::$my_name; } public static function getAge() { echo static::getName(); // static 关键字后期静态绑定 } } class Professor extends student { public static function getName() { return "The name of the student is : " . self::$my_name . " 并且年龄是24。"; } } student::getAge(); // The name of the student is : Joe echo "<br>"; // 使用static 关键字 它会根据上下文场景选择调用的哪个类中方法 Professor::getAge(); // The name of the student is : Joe 并且年龄是24。?>
1、self::
的限制
使用 self::
或者 __CLASS__
对当前类的静态引用,取决于定义当前方法所在的类:
<?phpclass A { public static function who() { echo __CLASS__; } public static function test() { self::who(); }}class B extends A { public static function who() { echo __CLASS__; }}B::test(); // A?>
举个栗子 (单例对象)
class Father { protected static $_instance = null; static public function getInstance() { if(self::$_instance === null) { //获取类的实例 self 始终永远和当前类进行绑定 //1. self 始终永远和当前类进行绑定 //2. self 在静态继承上下文中, 不能动态的识别或者设置静态成员的调用者 self::$_instance = new self(); } return self::$_instance; }}class son1 extends Father { protected static $_instance = null;}class son2 extends Father{ protected static $_instance = null;}var_dump(Father::getInstance());var_dump(Son1::getInstance());var_dump(Son2::getInstance());/* object(Father)#1 (0) { } object(Father)#1 (0) { } object(Father)#1 (0) { } *///使用self 实例化得到的都是Father类的同一个对象 (单例继承)
2、后期静态绑定的用法
后期静态绑定本想通过引入一个新的关键字表示运行时最初调用的类来绕过限制。简单地说,这个关键字能够让你在上述例子中调用 test()
时引用的类是 B
而不是 A
。最终决定不引入新的关键字,而是使用已经预留的 static
关键字。
<?phpclass A { public static function who() { echo __CLASS__; } public static function test() { static::who(); // 后期静态绑定从这里开始 }}class B extends A { public static function who() { echo __CLASS__; }}B::test(); // B?>
注意:在非静态环境下,所调用的类即为该对象实例所属的类。由于
$this->
会在同一作用范围内尝试调用私有方法,而static::
则可能给出不同结果。另一个区别是static::
只能用于静态属性。
后期静态绑定 static
前提:在静态的继承上下文中
目的:用于在继承范围内引用静态调用的类
所以:可以用后期静态绑定去解决单例继承的问题
class Father { protected static $_instance = null; static public function getInstance() { if(static::$_instance === null) { static::$_instance = new static(); } return static::$_instance; }}class son1 extends Father { protected static $_instance = null;}class son2 extends Father{ protected static $_instance = null;}var_dump(Father::getInstance()); // object(Father)#1 (0) { }var_dump(Son1::getInstance()); // object(son1)#2 (0) { }var_dump(Son2::getInstance()); // object(son2)#3 (0) { }
PHP 设计模式之单例模式
单例模式也叫单子模式,是一种常用的软件设计模式。 在应用这个模式时,可以确保一个类只能创建一个对象,这么做可以极大节省内存空间,有利于我们协调系统的整体行为。
使用单例模式创建的类(“单例类”)不能再其它类中直接实例化,只能被其自身实例化。它不会创建实例副本,而是会向单例类内部存储的实例返回一个引用。
单例模式一个主要应用场合就是应用程序与数据库打交道的场景,在一个应用中会存在大量的数据库操作,针对数据库句柄连接数据库的行为,使用单例模式可以避免大量的 new 操作,因为每一次 new 操作都会消耗系统和内存的资源。
实现单例模式的思路(三私一公):
私有的静态的对象实例;
私有的构造方法,在类外不能使用 new 创建对象;
私有的克隆方法,在类外不能使用 clone 克隆对象;
公共的静态的创建对象实例的方法。
使用单例模式创建一个数据库连接类 (pdo)
<?phpinterface iDbBase{ // 数据库操作 curd static function insert($db,$data); static function select($db,$where=[]); static function delete($db,$where=[]); static function update($db,$data,$where); static function doConnect($dsn,$username,$password); // return $this;实现链式调用 } // 使用单例模式连接数据库 abstract class Db implements iDbBase{ //创建类的唯一实例 唯一对象 // 保存返回的pdo连接对象 private static $instance; // private关键字 阻止此类在外部进行实例化 private function __construct(){} // private关键字阻止此类在外部进行克隆 private function __clone(){} //创建唯一的入口 公共静态方法 只能由类的自身 来进行实例化 public static function doConnect($dsn,$username,$password){ //判断$instance是否是aDb类的对象 if(empty(self::$instance)) { echo "我被实例化了……<br>"; //实例化本类, 传入连接参数, self::$instance = new PDO($dsn,$username,$password); } return self::$instance; } }// ------------------------------------------------------------// 数据库操作工作类class Mydb extends Db{ // 数据库操作 curd static function insert($db,$data) { $str = ""; foreach ($data as $key => $value) { $str .= "'".$value."',"; } $str = substr($str,0,-1); return $db->query("INSERT INTO `blogs` VALUES ($str)")->fetchAll(PDO::FETCH_ASSOC); } static function select($db,$where=[]) { $str = ''; if (!empty($where)) { foreach ($where as $key => $value) { if(is_array($where)){ if(count($where)>1){ $str .= $key.'='.$value.'and '; }else{ $str .= $key.'='.$value; } } } } $where = empty($where)?"":"where $str"; return $db->query("SELECT * FROM `blogs` $where")->fetchAll(PDO::FETCH_ASSOC); } static function delete($db,$where=[]) { $str = ''; foreach ($where as $key => $value) { if(is_array($where)){ if(count($where)>1){ $str .= $key.'='.$value.'and '; }else{ $str .= $key.'='.$value; } } } $str = count($where)>1 ? substr($str,0,-4):$str; return $db->query("delete from blogs where $str")->fetchAll(PDO::FETCH_ASSOC); } static function update($db,$data,$where) { $d = ''; foreach ($data as $key => $value) { $d .= $key.'="'.$value.'",'; } $d = substr($d,0,-1); $w = ""; foreach ($where as $key => $value) { if(is_array($where)){ if(count($where)>1){ $w .= $key.'='.$value.'and '; }else{ $w .= $key.'='.$value; } } } $w = count($where)>1 ? substr($w,0,-4):$w; return $db->query("update blogs set $d where $w")->fetchAll(PDO::FETCH_ASSOC); }}// 客户端代码---------------------------// 连接参数$config = [ 'type'=> $type ?? 'mysql', 'host'=> $host ?? 'localhost', 'dbname'=> $dbname ?? 'myblog', 'username'=> $username ?? 'root', 'password'=> $password ?? 'root'];$dsn = sprintf('%s:host=%s;dbname=%s',$config['type'],$config['host'],$config['dbname']);$username = $config['username'];$password = $config['password'];var_dump(Db::doConnect($dsn,$username,$password));echo "<br>";var_dump(Db::doConnect($dsn,$username,$password));echo "<br>";var_dump(Db::doConnect($dsn,$username,$password));echo "<br>";var_dump(Db::doConnect($dsn,$username,$password));echo "<br>";var_dump(Db::doConnect($dsn,$username,$password));?>
由上图可以看出”我被实例化了……”只被输出了一次,说明PDO对象也就实例化了一次,虽然调用了很多次。所以说使用单例模式可以保证一个类只能创建一个对象,不能创建第二个对象。
客户端代码
$config = [ 'type'=> $type ?? 'mysql', 'host'=> $host ?? 'localhost', 'dbname'=> $dbname ?? 'myblog', 'username'=> $username ?? 'root', 'password'=> $password ?? 'root'];$dsn = sprintf('%s:host=%s;dbname=%s',$config['type'],$config['host'],$config['dbname']);$username = $config['username'];$password = $config['password'];$db = Mydb::doConnect($dsn,$username,$password);// 查询print_r(Mydb::select($db));// 可选参数查询条件print_r(Mydb::select($db,["id"=>5]));
// 增加Mydb::insert($db,['6', '2', 'php设计模式', 'PHP设计模式之单例模式创建数据库连接', '2021-05-09 16:19:24']);print_r(Mydb::select($db));
// 删除Mydb::delete($db,['id'=>6]);print_r(Mydb::select($db));
// 修改Mydb::update($db,['user_id'=>2,'title'=>"php设计模式666","content"=>"php 设计模式之单例模式连接数据库操作",'create_time'=>"2021-05-09 17:20:00"],["id"=>5]);print_r(Mydb::select($db));
数据库脚本文件sql
SET FOREIGN_KEY_CHECKS=0;-- ------------------------------ Table structure for blogs-- ----------------------------DROP TABLE IF EXISTS `blogs`;CREATE TABLE `blogs` ( `id` int(11) NOT NULL AUTO_INCREMENT, `user_id` int(11) NOT NULL, `title` varchar(128) NOT NULL, `content` text, `create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `user_id` (`user_id`), CONSTRAINT `blogs_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`)) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8;-- ------------------------------ Records of blogs-- ----------------------------INSERT INTO `blogs` VALUES ('1', '1', '可用的路由方法', '路由器允许你注册能响应任何', '2020-09-26 16:43:00');INSERT INTO `blogs` VALUES ('3', '1', '基本路由', '构建基本路由只需要一个 URI 与一个 闭包', '2020-09-26 16:42:03');INSERT INTO `blogs` VALUES ('4', '2', 'web前端开发', 'HTML、css、js', '2020-09-30 22:34:46');INSERT INTO `blogs` VALUES ('5', '1', '重定向路由', '如果要定义重定向到另一个 URI 的路由', '2020-09-26 17:09:49');-- ------------------------------ Table structure for users-- ----------------------------DROP TABLE IF EXISTS `users`;CREATE TABLE `users` ( `id` int(11) NOT NULL AUTO_INCREMENT, `account` varchar(20) NOT NULL, `password` varchar(32) NOT NULL, PRIMARY KEY (`id`)) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;-- ------------------------------ Records of users-- ----------------------------INSERT INTO `users` VALUES ('1', 'admin', '1234');INSERT INTO `users` VALUES ('2', 'user1', '0000');
PHP语言是一种解释型的脚本语言,这种运行机制使得每个PHP页面被解释执行后,所有的相关资源都会被回收。也就是说,PHP在语言级别上没有办法让某个对象常驻内存,这和asp.net、Java等编译型是不同的,比如在Java中单例会一直存在于整个应用程序的生命周期里,变量是跨页面级的,真正可以做到这个实例在应用程序生命周期中的唯一性。然而在PHP中,所有的变量无论是全局变量还是类的静态成员,都是页面级的,每次页面被执行时,都会重新建立新的对象,都会在页面执行完毕后被清空,这样似乎PHP单例模式就没有什么意义了。所以PHP单例模式只是针对单次页面级请求时,出现多个应用场景并需要共享同一对象资源时是非常有意义的
__clone() 魔术方法
clone() 方法不能够直接被调用,只有当通过 clone 关键字克隆一个对象时才可以使用该对象调用 clone() 方法。当创建对象的副本时,PHP 会检查 clone() 方法是否存在。如果不存在,那么它就会调用默认的 clone() 方法,复制对象的所有属性。如果 clone() 方法已经定义过,那么 clone() 方法就会负责设置新对象的属性。所以在 __clone() 方法中,只需要覆盖那些需要更改的属性就可以了。
__clone() 方法不需要任何参数,下面通过一个示例来演示一下:
<?php class Website{ public $name, $url; public function __construct($name, $url){ $this -> name = $name; $this -> url = $url; } public function output(){ echo $this -> name.','.$this -> url.'<br>'; } public function __clone(){ $this -> name = 'PHP教程'; $this -> url = 'http://c.biancheng.net/'; } } $obj = new Website('php中文网', 'http://php.cn/'); $obj2 = clone $obj; $obj -> output(); $obj2 -> output();?>
提示:如果在类中设置一个空的,访问权限为 private(私有的)的 __clone() 方法的话,可以起到禁止克隆的作用。
面向对象编程总结
class: 类声明
new: 类实例化
public: 公开成员,随处可见
protected: 受保护成员,仅在当前类或子类中可见
private: 私有成员, 仅在当前类可见
spl_autoload_register(): 自动加载器
extends: 类的扩展
abstract 抽象类
interface 接口
static: 声明类的静态成员
$this: 实例引用
self: 类的引用
trait: 类功能横向扩展