前言
我相信很多同行去另一家公司之后都会吐槽原来的代码写的太菜。这其实很大一部分原因都来自公司要求“小步快跑,快速迭代”。不同的人重复造轮子,代码质量参差不齐,各种胶水式的代码遍布SVN和Git。大家不敢动原来的代码,只能在最后的地方进行修修补补,所以导致维护困难。
一、面向对象的概念
对象:对象是一些变量和方法都聚集在一起的实体
类:
属性:一个属性是类中的一个内部变量。但从本质上讲,属性是指在类本身,而不是在这个类中任何方法声明的变量;
引用:两个不同的变量名字指向相同的内容;
特点:
1、封装、通过修饰符改变属性或者函数的访问权限,达到保护的作用。
权限修饰符
private: 只能在自己的本类里才能被访问到;
protected: 本类和子类能访问;
public: 本类和子类,还有类外部都可以访问到;
2、继承:子类继承父类的属性和方法,再进一步拓展自己的属性和方法;
1、子类继承父类,子类有父类所有的属性和方法,在子类里面能够操作父类中不是private修饰的属性或者方法;
2、子类的方法会覆盖父类的方法;
3、php不会在子类的构造方法中自动调用父类的构造方法。要执行父类的构造方法,需要在子类的构造方法中调用parent::__construct();
parent:__destruct();
3、多态:通过继承复用实现的;同一个操作作用于不同的类的实例,将产生不同的执行结果。或者不同类的对象收到相同的消息时,将得到不同的结果。
<?php
class animal{
function can(){
echo "this function weill be re-write in the children";
}
}
class cat extends animal{
function can(){
echo "I can climb";
}
}
class dog extends animal{
function can(){
echo "I can swim";
}
}
function test($obj){
$obj->can();
}
test(new cat());
test(new dog()); 同一个操作作用于不同类的实例,结果也不相同
?>
二、构造方法和析构方法
构造函数:__construct() 主要用来在创建对象时初始化对象,即为对象成员变量赋值,在创建对象的语句中与new运算符一起使用;
析构函数:__destruct() 在某个对象的所有引用都被删除或者当对象被显示销毁时执行;
如何销毁对象
1、unset(),直接赋值为null或者其他值;
2、执行完最后一行代码时进行自动销毁;
$a = new People();
$a = $b = $c;
unset($a);
//此时unset($a)只是$a=null不再指向此对象,但其他变量$b,$c仍然指向此对象,所以该对象不能销毁;
析构函数即使在使用exit()终止脚本运行时也会被调用
php8对构造函数的属性有了进一步的提升:
简单的数值对象:正常的流程:定义变量,构造函数参数定义,给参数赋值;
class Person{
public string $name;
public int $age;
public function __construct($name = '',$age = 0){
$this->name = $name;
$this->age = $age;
}
}
提升后的:一次性完成了成员变量定义,参数定义以及赋值的三个过程
class Person{
public function __construct(private string name = '',public int $age = 0 ){
}
}
放在构造器提升参数里的属性会同时复制为属性和参数
三、防止重写和被扩展 final
1、如果父类中的方法被声明为final,则不允许被子类方法覆盖。如果一个类被声明为final,则不能被继承;
理解:final意思为最终的,那就是不能对她进行任何操作。
四、接口
1、接口可以指定某个类必须实现哪些方法,但不需要定义这些方法的具体内容;
2、接口通过interface关键字来定义,但其中定义的所有的方法都是空的
3、接口中定义的方法必须是公有的
4、实现一个接口,使用implements操作符
5、类中必须实现接口中定义的所有方法
6、类可以实现多个接口,用逗号来分隔多个接口的名称
<?php
interface Fruit
{
const MAX_WEIGHT = 5; //此处不用声明,就是一个静态常量
function setName($name);
function getName();
}
//实现接口
class Apple implements Fruit
{
private $name;
function getName() {
return $this->name;
}
function setName($_name) {
$this->name = $_name;
}
}
$apple = new Apple(); //创建对象
$apple->setName("苹果");
echo "创建了一个" . $apple->getName();
echo "<br />";
echo "MAX_GRADE is " . Apple::MAX_WEIGHT; //静态常量
//实现多个接口
Class Template implements iTemplate,yTemplate{
private $age = 19;
public function setName(){
return $this->age;
}
public function getName($){
return 'age is '."$age";
}
}
五、抽象类
1、任何一个类,里面至少有一个方法是被声明为抽象的,则这个类就必须被声明为抽象类;
2、定义为抽象的类不能被实例化;必须先继承抽象来,再实例化子类;
3、被定义为抽象的方法只是声明了其调动的方式,不能定义其具体的功能实现;
4、继承一个抽象类的类,子类必须定义父类中的所有抽象方法;并且这些方法的访问控制必须和父类一样或者更为宽松;比如某个抽象方法被声明为受保护的,那么子类中实现的方法就必须为受保护的或者公有的,不能定义为私有的;
abstract class AbstractClass{
// 定义抽象方法
abstract protected function getValue();
// 普通方法
public function printOut(){
print $this->getValue()."";
}
}
class ConcreteClass extends AbstractClass{
protected function getValue(){
return "abstract ";//抽象方法的实现
}
}
$class1 = new ConcreteClass;
$class1->printOut();
六、traits代码复用
1、php一直是单继承,无法同时从两个基类中继承属性和方法
2、trait不能实例化,通过在类中使用use关键字声明要组合的trait类名;
trait Driver{
public $carName = "DMW";
public function driving(){
echo "driving {$this->carName}";
}
}
class Person{
public function age(){
echo "I am 18 years old";
}
}
class Student extends Person(){
use Driver;
public function study(){
echo "Learn to drive ";
}
}
$student = new Student();
$student->study();
$student->age();
$student->driving();
结果:
Learn to drive;
i am 18 years old;
driving BMW;
3、当方法或者属性同名时,当前类中的方法会覆盖trait的方法,而trait的方法又覆盖了基类的方法;
4、组合多个trait,可用都好分割,use trait1,trait2;
5、insteadof是使用某个方法替代另一个,而as是给方法取一个别名;
<?php
trait Trait1 {
public function hello() {
echo "Trait1::hello \n";
}
public function hi() {
echo "Trait1::hi \n";
}
}
trait Trait2 {
public function hello() {
echo "Trait2::hello\n";
}
public function hi() {
echo "Trait2::hi\n";
}
}
class Class1 {
use Trait1, Trait2 {
Trait2::hello insteadof Trait1;
Trait1::hi insteadof Trait2;
}
}
class Class2 {
use Trait1, Trait2 {
Trait2::hello insteadof Trait1;
Trait1::hi insteadof Trait2;
Trait2::hi as hei;
Trait1::hello as hehe;
}
}
$Obj1 = new Class1();
$Obj1->hello();
$Obj1->hi();
echo "\n";
$Obj2 = new Class2();
$Obj2->hello();
$Obj2->hi();
$Obj2->hei();
$Obj2->hehe();
结果:
Trait2::hello
Trait1::hi
Trait2::hello
Trait1::hi
Trait2::hi
Trait1::hello
<?php
trait Hello {
public function hello() {
echo "hello,我是周伯通\n";
}
}
class Class1 {
use Hello {
hello as protected;
}
}
class Class2 {
use Hello {
Hello::hello as private hi;
}
}
$Obj1 = new Class1();
$Obj1->hello(); # 报致命错误,因为hello方法被修改成受保护的
$Obj2 = new Class2();
$Obj2->hello(); # 输出: hello,我是周伯通,因为原来的hello方法仍然是公共的
$Obj2->hi(); # 报致命错误,因为别名hi方法被修改成私有的
一篇比较好的文章有助于理解traits
七、静态方法和属性
1、声明类属性或方法为static,就可以不实例化类而直接访问;
2、无论函数调用多少次,只初始化一次;
八、魔术方法
定义:某些情况下,会自动调用的方法,称为魔术方法;所有的魔术方法,必须声明为public
魔术方法的参数都不能通过引用传递。
魔术方法不能被声明为static
__clone:克隆方法,当对象被克隆时,将会自动调用;
class Human{
var $age = 22;
public function __clone(){
echo "有人克隆我,假冒";
}
}
$a = new Human();
$b = clone $a;
echo $b;
结果:有人克隆我,假冒
拓展:深拷贝和浅拷贝
深拷贝:赋值时值被完全赋值,完全的copy,对其中一个做出改变,不会影响另一个
浅拷贝:赋值时,引用赋值。对其中的一个修改,会影响另一个
=赋值时,普通对象是深拷贝,但对对象来说,是浅拷贝。
$m =1;
$n = $m ;此时$n为1
$n = 2;
echo $m ;只是值进行赋值,深拷贝 ,值为1
_______________________________
class Test{
public $a = 1;
}
$m = new Test();
$n = $m;引用赋值
$m->a = 2;
echo $n->a ;结果为2.浅拷贝
__get();当我们调用一个权限上不允许调用的属性或者不存的属性时,会直接走__get()魔术方法取值;
class Human{
var $age = 22;
protected $name = "duanjiaqi";
private $sex = "1";
public function __get($p){
echo '您是想访问我的'.$p.'属性';
}
}
$a = new Human();
echo $a->age.PHP_EOL;//此时age的权限是可以访问的,所以过为22;
echo $a->name.PHP_EOL;//如果没有__get()魔术方法的话,在编辑器里name直接报错,因为属性权限为受保护的,类外部是访问不到的;有__get()魔术方法的话,结果为:您是想访问我的name属性
echo $a->sex.PHP_EOL;
echo $a->no;//类内部没有no属性,没有__get()魔术方法的话直接报错;在__get()魔术方法存在的话,结果为:您是想访问我的no属性
__set():当为无权操作的属性或者不存在的属性赋值时,自动调用__set()魔术方法;
class Human{
var $age = 22;
protected $name = "duanjiaqi";
private $sex = "1";
public function __set($a,$b){
echo "设置的".$a."属性,";
echo "值为".$b.".";
}
}
$a = new Human();
$a->sex = 33;//sex属性为private,类外部访问不到,所以直接走__set()方法,为sex赋值;结果为:设置的sex属性,值为33.
__isset():当对不可访问或者不存在的属性调用isset()或empty()时,isset()或被调用;
__unset():当对不可访问或者不存在的属性调用unset()时,__unset()会被调用
class Human{
private $age = 22;
public function __isset($name)
{
// TODO: Implement __isset() method.
echo 'unset';
}
}
$a = new Human();
isset($a->age);//如果$age的属性为private或者protected时,则结果为unset,如果$age的属性是public时,则结果为空;
__call():调用不可见或者不存在或者无权限的方法时,自动调用;
__callStatic(): 在静态上下文中调用一个不可访问的方法时,__callStatic()会被调用。
class MethodTest
{
public function __call($name, $arguments)
{
// 注意: $name 的值区分大小写
echo "Calling object method '$name' "
. implode(', ', $arguments). "\n";
}
public static function __callStatic($name, $arguments)
{
// 注意: $name 的值区分大小写
echo "Calling static method '$name' "
. implode(', ', $arguments). "\n";
}
}
$obj = new MethodTest;
$obj->runTest('in object context');
MethodTest::runTest('in static context');
结果:Calling object method 'runTest' in object context
Calling static method 'runTest' in static context
?>
__sleep()和__wakeup() serialize()与unserialize()
1、serialize()函数会检查类中是否存在魔术方法__sleep(),如果存在,则该方法会先被调用,然后才执行序列化操作;unserialize()会检查是否存在一个__wakeup()方法,如果存在,则会先调用__wakeup方法,预先准备对象需要的资源。
2、__sleep()常用语提交未提交的数据
__toString():方法用于一个类被当成字符串时应怎样回应
class TestClass{
public $foo;
public function __construct($foo){
$this->foo = $foo;
}
public function __toString(){
return $this->foo;
}
}
$class = new TestClass('Hello');
echo $class;
结果为:Hello;