再探面向对象

本文详细探讨了PHP中的面向对象编程,包括类的定义、实例化、继承、类的自动加载、构造函数和析构函数等核心概念。还讲解了访问控制、静态属性和方法、接口、Trait的使用以及如何解决冲突。此外,介绍了匿名类、重载、对象遍历和魔术方法等高级特性,帮助读者全面掌握PHP面向对象编程。
摘要由CSDN通过智能技术生成
类的定义

每个类的定义都以关键字 class 开头,后面跟着类名,后面跟着一对花括号,里面包含有类的属性与方法的定义
一个类可以包含有属于自己的常量,变量(属性)以及函数(方法)
当一个方法或属性在类定义内部被调用时,有一个可用的伪变量 $this
面向对象的基本原则:高内聚,低耦合

class SimpleClass {
    //声明属性
    public $var = 'var is a default value';

    //声明方法
    public function displayVar()
    {
        echo $this->var;
    }
}
类的实例化

要创建一个类的实例,必须使用 new 关键字。当创建新对象时该对象总是被赋值,除非该对象定义了构造函数并且在出错时抛出了一个异常。类应在被实例化之前定义(某些情况下则必须这样)。
如果在 new 之后跟着的是一个包含有类名的字符串 string,则该类的一个实例被创建。如果该类属于一个命名空间,则必须使用其完整名称
在类定义内部,可以用 new self 和 new parent 创建新对象
当把一个对象已经创建的实例赋给一个新变量时,新变量会访问同一个实例,就和用该对象赋值一样。此行为和给函数传递入实例时一样。
可以用克隆给一个已创建的对象建立一个新实例
instanceof 用于确定一个 PHP 变量是否属于某一类 class 的实例,instanceof 也可用来确定一个变量是不是继承自某一父类的子类的实例

$instance = new SimpleClass();

class GrandParent{}

class ParentClass extends GrandParent{}

class MyClass extends ParentClass{}

$a = new MyClass;

var_dump($a instanceof MyClass);//true
var_dump($a instanceof ParentClass);//true
var_dump($a instanceof GrandParent);//true

继承

一个类可以在声明中用 extends 关键字继承另一个类的方法和属性。PHP不支持多重继承,一个类只能继承一个基类
被继承的方法和属性可以通过用同样的名字重新声明被覆盖。但是如果父类定义方法时使用了 final,则该方法不可被覆盖。可以通过 parent:: 来访问被覆盖的方法或属性
当覆盖方法时,参数必须保持一致否则 PHP 将发出 E_STRICT 级别的错误信息。但构造函数例外,构造函数可在被覆盖时使用不同的参数

::class

自 PHP 5.5 起,关键词 class 也可用于类名的解析。使用 ClassName::class 你可以获取一个字符串,包含了类 ClassName 的完全限定名称。这对使用了 命名空间 的类尤其有用

namespace app;

class SimpleClass {}
echo SimpleClass::class;//打印出 app\SimpleClass
属性

类的变量成员叫做属性,属性声明是由关键字 public,protected 或者 private 开头,然后跟一个普通的变量声明来组成,属性中的变量可以初始化,但是初始化的值必须是常数
在类的成员方法里面,可以用 ->(对象运算符): t h i s − > p r o p e r t y ( 其 中 p r o p e r t y 是 该 属 性 名 ) 这 种 方 式 来 访 问 非 静 态 属 性 。 静 态 属 性 则 是 用 : : ( 双 冒 号 ) : s e l f : : this->property(其中 property 是该属性名)这种方式来访问非静态属性。静态属性则是用 ::(双冒号):self:: this>propertyproperty访::self::property 来访问

类常量

可以把在类中始终保持不变的值定义为常量。在定义和使用常量的时候不需要使用 $ 符号
常量的值必须是一个定值,不能是变量,类属性,数学运算的结果或函数调用
接口(interface)中也可以定义常量
内部访问用self::,外部访问用类名::

class SimpleClass{
    const CONSTANT = 'constant value';

    public function getConstant()
    {
        return self::CONSTANT;
    }
}

echo SimpleClass::CONSTANT;
$obj = new SimpleClass();
echo $obj->getConstant();
类的自动加载

在编写面向对象(OOP) 程序时,很多开发者为每个类新建一个 PHP 文件。 这会带来一个烦恼:每个脚本的开头,都需要包含(include)一个长长的列表(每个类都有个文件)
spl_autoload_register() 函数可以注册任意数量的自动加载器,当使用尚未被定义的类(class)和接口(interface)时自动去加载

spl_autoload_register(function($className){
    if(file_exists($className.'.php')){
        require_once $className.'.php';
    }else{
        throw new Exception('没找到相关的 '.$className.' 类文件');
    }
});

try{
    $obj = new SimpleClass();
}catch(Exception $e){
    echo $e->getMessage();
}
构造函数和析构函数

具有构造函数__construct的类会在每次创建新对象时先调用此方法,所以非常适合在使用对象之前做一些初始化工作
如果子类中定义了构造函数则不会隐式调用其父类的构造函数。要执行父类的构造函数,需要在子类的构造函数中调用 parent::__construct()。如果子类没有定义构造函数则会如同一个普通的类方法一样从父类继承(假如没有被定义为 private 的话)
析构函数__destruct类似于其它面向对象的语言,如 C++。析构函数会在到某个对象的所有引用都被删除或者当对象被显式销毁时执行
和构造函数一样,父类的析构函数不会被引擎暗中调用。要执行父类的析构函数,必须在子类的析构函数体中显式调用 parent::__destruct()。此外也和构造函数一样,子类如果自己没有定义析构函数则会继承父类的
析构函数即使在使用 exit() 终止脚本运行时也会被调用。在析构函数中调用 exit() 将会中止其余关闭操作的运行
试图在析构函数(在脚本终止时被调用)中抛出一个异常会导致致命错误

访问控制

对属性或方法的访问控制,是通过在前面添加关键字 public(公有),protected(受保护)或 private(私有)来实现的。被定义为公有的类成员可以在任何地方被访问。被定义为受保护的类成员则可以被其自身以及其子类和父类访问。被定义为私有的类成员则只能被其定义所在的类访问

继承

继承将会影响到类与类,对象与对象之间的关系,当扩展一个类,子类就会继承父类所有公有的和受保护的方法。除非子类覆盖了父类的方法,被继承的方法都会保留其原有功能,继承对于功能的设计和抽象是非常有用的,而且对于类似的对象增加新功能就无须重新再写这些公用的功能
除非使用了自动加载,否则一个类必须在使用之前被定义。如果一个类扩展了另一个,则父类必须在子类之前被声明。此规则适用于类继承其它类与接口

范围解析操作符::

可以用于访问静态成员,类常量,还可以用于覆盖类中的属性和方法
当在类定义之外引用到这些项目时,要使用类名
self,parent 和 static 这三个特殊的关键字是用于在类定义的内部对其属性或方法进行访问的

static 关键字

用 static 关键字来定义静态方法和属性。static 也可用于定义静态变量以及后期静态绑定
声明类属性或方法为静态,就可以不实例化类而直接访问。静态属性不能通过一个类已实例化的对象来访问(但静态方法可以)
由于静态方法不需要通过对象即可调用,所以伪变量 $this 在静态方法中不可用
静态属性不可以由对象通过 -> 操作符来访问
就像其它所有的 PHP 静态变量一样,静态属性只能被初始化为文字或常量,不能使用表达式。所以可以把静态属性初始化为整数或数组,但不能初始化为另一个变量或函数返回值,也不能指向一个对象

抽象类

抽象的类不能被实例化
任何一个类,如果它里面至少有一个方法是被声明为抽象的,那么这个类就必须被声明为抽象的。被定义为抽象的方法只是声明了其调用方式(参数),不能定义其具体的功能实现
继承一个抽象类的时候,子类必须定义父类中的所有抽象方法
这些方法的访问控制必须和父类中一样(或者更为宽松)
方法的调用方式必须匹配,即类型和所需参数数量必须一致

对象接口

使用接口(interface),可以指定某个类必须实现哪些方法,但不需要定义这些方法的具体内容。
接口是通过 interface 关键字来定义的,就像定义一个标准的类一样,但其中定义所有的方法都是空的
接口中定义的所有方法都必须是公有,这是接口的特性
要实现一个接口,使用 implements 操作符。类中必须实现接口中定义的所有方法,否则会报一个致命错误。类可以实现多个接口,用逗号来分隔多个接口的名称
接口中也可以定义常量。接口常量和类常量的使用完全相同,但是不能被子类或子接口所覆盖

Trait

Trait 是为类似 PHP 的单继承语言而准备的一种代码复用机制。Trait 为了减少单继承语言的限制,使开发人员能够自由地在不同层次结构内独立的类中复用 method
Trait 优先级,从基类继承的成员会被 trait 插入的成员所覆盖。优先顺序是来自当前类的成员覆盖了 trait 的方法,而 trait 则覆盖了被继承的方法
多个trait,通过逗号分隔,在 use 声明列出多个 trait,可以都插入到一个类中

trait ezcReflectionReturnInfo {
    function getReturnType() { /*1*/ }
    function getReturnDescription() { /*2*/ }
}

class ezcReflectionMethod extends ReflectionMethod {
    use ezcReflectionReturnInfo;
    /* ... */
}

class ezcReflectionFunction extends ReflectionFunction {
    use ezcReflectionReturnInfo;
    /* ... */
}
Trait冲突解决

如果两个 trait 都插入了一个同名的方法,如果没有明确解决冲突将会产生一个致命错误。
为了解决多个 trait 在同一个类中的命名冲突,需要使用 insteadof 操作符来明确指定使用冲突方法中的哪一个。
以上方式仅允许排除掉其它方法,as 操作符可以 为某个方法引入别名。 注意,as 操作符不会对方法进行重命名,也不会影响其方法

trait A {
    public function smallTalk() {
        echo 'a';
    }
    public function bigTalk() {
        echo 'A';
    }
}

trait B {
    public function smallTalk() {
        echo 'b';
    }
    public function bigTalk() {
        echo 'B';
    }
}

class Talker {
    use A, B {
        B::smallTalk insteadof A;
        A::bigTalk insteadof B;
    }
}

class Aliased_Talker {
    use A, B {
        B::smallTalk insteadof A;
        A::bigTalk insteadof B;
        B::bigTalk as talk;
    }
}
匿名类

匿名类被嵌套进普通 Class 后,不能访问这个外部类(Outer class)的 private(私有)、protected(受保护)方法或者属性。 为了访问外部类(Outer class)protected 属性或方法,匿名类可以 extend(扩展)此外部类。 为了使用外部类(Outer class)的 private 属性,必须通过构造器传进来

class Outer
{
    private $prop = 1;
    protected $prop2 = 2;

    protected function func1()
    {
        return 3;
    }

    public function func2()
    {
        return new class($this->prop) extends Outer {
            private $prop3;

            public function __construct($prop)
            {
                $this->prop3 = $prop;
            }

            public function func3()
            {
                return $this->prop2 + $this->prop3 + $this->func1();
            }
        };
    }
}

echo (new Outer)->func2()->func3();
重载

PHP所提供的重载(overloading)是指动态地创建类属性和方法。我们是通过魔术方法(magic methods)来实现的
所有的重载方法都必须被声明为 public
重载分为属性重载和方法重载

遍历对象

用 foreach 语句。默认情况下,所有可见属性都将被用于遍历

魔术方法

__construct(), __destruct(), __call(), __callStatic(), __get(), __set(), __isset(), __unset(), __sleep(), __wakeup(), __toString(), __invoke(), __set_state(), __clone() 和 __debugInfo() 等方法在 PHP 中被称为魔术方法(Magic methods)。在命名自己的类方法时不能使用这些方法名,除非是想使用其魔术功能

Final关键字

如果父类中的方法被声明为 final,则子类无法覆盖该方法。如果一个类被声明为 final,则不能被继承
属性不能被定义为 final,只有类和方法才能被定义为 final

对象赋值

对象复制可以通过 clone 关键字来完成(如果可能,这将调用对象的 __clone() 方法)。对象中的 __clone() 方法不能被直接调用。

类型约束

函数的参数可以指定必须为对象(在函数原型里面指定类的名字),接口,数组(PHP 5.1 起)或者 callable(PHP 5.4 起)。不过如果使用 NULL 作为参数的默认值,那么在调用函数的时候依然可以使用 NULL 作为实参。
如果一个类或接口指定了类型约束,则其所有的子类或实现也都如此。
类型约束不能用于标量类型如 int 或 string。Traits 也不允许

后期静态绑定

后期静态绑定的功能,用于在继承范围内引用静态调用的类
后期静态绑定工作原理是存储了 在上一个“非转发调用”(non-forwarding call)的类名
当进行静态方法调用时,该类名即为明确指定的那个(通常在 :: 运算符左侧部分);当进行非静态方法调用时,即为该对象所属的类
转发调用(forwarding call)指的是通过以下几种方式进行的静态调用:self::,parent::,static:: 以及 forward_static_call()
后期绑定的意思是说,static::不再被解析为定义当前方法所在的类,而是在实际运行时计算的。也可以称之为“静态绑定”,因为它可以用于(但不限于)静态方法的调用
使用 self:: 或者 __CLASS__ 对当前类的静态引用,取决于定义当前方法所在的类
后期静态绑定的解析会一直到取得一个完全解析了的静态调用为止。另一方面,如果静态调用使用 parent:: 或者 self:: 将转发调用信息

对象和引用

PHP 的引用是别名,就是两个不同的变量名字指向相同的内容
在 PHP 5,一个对象变量已经不再保存整个对象的值。只是保存一个标识符来访问真正的对象内容
当对象作为参数传递,作为结果返回,或者赋值给另外一个变量,另外一个变量跟原来的不是引用的关系,只是他们都保存着同一个标识符的拷贝,这个标识符指向同一个对象的真正内容

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值