在本文中,我们讨论了PHP中的面向对象编程。
$ php -v php -v PHP 8.1.2 (cli) (built: Aug 8 2022 07:28:23) (NTS) ...
我们使用PHP版本8.1.2。
那里有三种广泛使用的编程范式:过程式、函数式和面向对象。PHP同时支持面向过程和面向对象的编程。
面向对象编程(OOP)是一种编程范例,它使用对象及其交互来设计应用程序和计算机程序。
OOP中的基本编程概念是:
- 抽象
- 多态
- 封装
- 继承
抽象是通过对适合问题的类进行建模来简化复杂的现实。多态性是针对不同的数据输入以不同的方式使用运算符或函数的过程。封装向其他对象隐藏类的实现细节。继承是一种使用已经定义的类来形成新类的方法。
PHP对象
对象是PHPOOP程序的基本构建块。对象是数据和方法的组合。在OOP程序中,我们创建对象。这些对象通过方法相互通信。每个对象都可以接收消息、发送消息和处理数据。
创建一个对象有两个步骤。首先,我们创建一个类。类是对象的模板。它是一个蓝图,描述了类的所有对象共享的状态和行为。一个类可以用来创建许多对象。在运行时从类创建的对象称为该特定类的实例。
<?php class Simple {} $object = new Simple(); print_r($object); echo gettype($object), "\n";
在我们的第一个示例中,我们创建了一个简单的对象。
class Simple {}
这是一个简单的类定义。模板的主体是空的。它没有任何数据或方法。
$object = new Simple();
我们创建了一个Simple
类的新实例。为此,我们有new
关键字。$object
变量是创建对象的句柄。
print_r($object); echo gettype($object), "\n";
我们使用print_r
函数获取有关对象的信息,使用gettype
函数获取变量的类型。
$ php simple_class.php Simple Object ( ) object
我们没有得到太多信息,因为类定义是空的。变量的类型是object
。
PHP对象属性
对象属性是捆绑在类实例中的数据。对象属性称为实例变量或成员字段。实例变量是在类中定义的变量,类中的每个对象都有一个单独的副本。
<?php class Person { public $name = ""; } $p1 = new Person(); $p1->name = "Jane"; $p2 = new Person(); $p2->name = "Beky"; echo $p1->name . "\n"; echo $p2->name . "\n";
在上面的PHP脚本中,我们有一个带有成员字段的Person
类。
$p1 = new Person(); $p1->name = "Jane";
我们创建了Person类的一个实例,并将$name
变量设置为“Jane”。我们使用->运算符来访问对象的属性。
$p2 = new Person(); $p2->name = "Beky";
我们创建了Person类的另一个实例。这里我们将变量设置为“Beky”。
echo $p1->name . "\n"; echo $p2->name . "\n";
我们将变量的内容打印到控制台。
$ php member_fields.php Jane Beky
我们看到了脚本的输出。Person
类的每个实例都有一个单独的$name
成员字段副本。
PHP方法
方法是在类体内定义的函数。它们用于对我们的对象的属性执行操作。方法在OOP范例的封装概念中是必不可少的。例如,我们的AccessDatabase类中可能有一个connect
方法。我们不需要知道connect
方法究竟是如何连接到数据库的。我们只知道它是用来连接数据库的。这是编程中必不可少的责任划分,尤其是在大型应用程序中。
<?php class Circle { public $radius; function setRadius($radius) { $this->radius = $radius; } function area() { return $this->radius * $this->radius * M_PI; } } $c = new Circle(); $c->setRadius(5); echo $c->area(), "\n";
在代码示例中,我们有一个Circle
类。我们定义了两种方法。
public $radius;
我们有一个成员字段。它是圆的半径。public
关键字是一个访问说明符。它表明该变量可以从外部世界完全访问。
function setRadius($radius) { $this->radius = $radius; }
这是setRadius
方法。这是一个普通的PHP函数。我们将类内定义的函数称为方法。$this
变量是一个特殊变量,我们用它来访问方法中的成员字段。
function area() { return $this->radius * $this->radius * M_PI; }
area
方法返回圆的面积。M_PI
是一个内置常量。
$ php circle.php 78.539816339745
PHP访问修饰符
访问修饰符设置方法和成员字段的可见性。PHP具有三个访问修饰符:public
、protected
和private
。可以从任何地方访问public
成员。protected
成员只能在类本身以及继承类和父类中访问。private
成员只能由定义该成员的类访问。
访问修饰符防止数据被意外修改。它们使程序更加健壮。
<?php class Person { public $name = ""; private $age; } $p = new Person(); $p->name = "Jane"; #$p->age = 17; echo $p->name . "\n";
在上面的PHP脚本中,我们有两个成员字段;一个是公开的,另一个是私有的。
$p->name = "Jane"; #$p->age = 17;
我们从外部世界访问$name
成员。外面的世界,我们的意思是“不在课堂上”。没关系,因为$name
变量被声明为public
。无法访问$age
成员。private
修饰符禁止这样做。如果我们取消注释代码行,我们会收到“致命错误:无法访问私有属性Person::$age”错误。
<?php class Base { public $name = "Base"; protected $id = 6124; private $is_defined = "yes"; } class Derived extends Base { public function info() { echo "This is Derived class\n"; echo "Members inherited: \n"; echo $this->name . "\n"; echo $this->id . "\n"; echo $this->is_defined . "\n"; } } $derived = new Derived(); $derived->info();
在这个PHP脚本中,我们有一个Derived
类,它扩展了Base
类。Base
类有三个成员字段,都具有不同的访问修饰符。$is_defined
成员不是继承的。private
修饰符可以防止这种情况。
public function info() {
info
方法有一个public
访问修饰符。这意味着它可以在类环境之外调用。
$ php access2.php This is Derived class Members inherited: Base 6124
public和protected成员是继承的,private成员不是。
<?php class SysInfo { private function get_date() { return date("Y/m/d"); } private function get_version() { return phpversion(); } public function getInfo() { $date = $this->get_date(); $version = $this->get_version(); echo "The date is: $date\n"; echo "The PHP version is: $version\n"; } } $sys = new SysInfo(); $sys->getInfo(); #$sys->get_date();
在这个脚本中,我们有一个SysInfo
类。它向控制台输出一些系统信息。我们有两个私人活动和一个公共活动。此处的私有方法仅用于SysInfo
类的内部工作。他们不应该在课堂外被调用。
$sys = new SysInfo(); $sys->getInfo(); #$sys->get_date();
我们创建了一个SysInfo
类的实例,并调用公开可用的getInfo
方法。getInfo
方法在内部使用私有方法来完成它的工作。取消注释最后一行代码会导致错误。
PHP方法重载
方法重载允许创建多个具有相同名称但输入类型互不相同的方法。
方法重载有什么用?Qt4库提供了一个很好的用法示例。QPainter
类具有三种绘制矩形的方法。它们的名称是drawRect
并且它们的参数不同。一个引用浮点矩形对象,另一个引用整数矩形对象,最后一个引用四个参数,x、y、宽度、高度。如果Qt的开发语言C++语言没有方法重载,则库的创建者必须将方法命名为drawRectRectF
、drawRectRect
、drawRectXYWH。方法重载的解决方案更优雅。
<?php class Sum { public function getSum() { return 0; } public function getSum($x) { return $x; } public function getSum($x, $y) { return $x + $y; } } $s = new Sum(); echo $s->getSum() . "\n" ; echo $s->getSum(5) . "\n" ; echo $s->getSum(3, 4) . "\n" ;
这是一种方法重载,我们从C#、Java或C++等语言中了解到。但这在PHP中不起作用。运行此示例,我们收到以下错误:“致命错误:无法重新声明Sum::getSum()”。默认情况下,PHP函数可以采用任意数量的变量。
为了在PHP中模拟方法重载,我们使用func_get_args
函数。
<?php class Sum { public function getSum() { $sum = 0; $args = func_get_args(); if (empty($args)) return 0; foreach ($args as $arg) { $sum += $arg; } return $sum; } } $s = new Sum(); echo $s->getSum() . "\n" ; echo $s->getSum(5) . "\n" ; echo $s->getSum(3, 4) . "\n" ; echo $s->getSum(3, 4, 7) . "\n" ;
这一次,脚本将运行。
$args = func_get_args();
func_get_args
函数返回一个包含函数参数列表的数组。
foreach ($args as $arg) { $sum += $arg; }
我们遍历数组的所有成员,并计算总和。
echo $s->getSum() . "\n" ; echo $s->getSum(5) . "\n" ; echo $s->getSum(3, 4) . "\n" ; echo $s->getSum(3, 4, 7) . "\n" ;
我们使用不同数量的输入调用相同的方法名称。
$ php overloading2.php 0 5 7 14
PHP构造器
构造函数是一种特殊的方法。创建对象时会自动调用它。构造函数的目的是初始化对象的状态。PHP中构造函数的名称是__construct
(带两个下划线)。
<?php class Song { function __construct() { echo "Song object is created \n"; } } $song = new Song();
我们有一个Song
类。此类具有将消息打印到控制台的构造函数。
$song = new Song();
这是创建对象并调用构造函数的时间。我们在控制台中收到一条消息。
$ php constructor.php Song object is created
构造函数经常接受参数。
<?php class Song { function __construct($song) { echo "Song $song is created \n"; } } $song = new Song("Bad romance");
我们稍微修改一下前面的例子。我们将一个值传递给构造函数。
function __construct($song) { echo "Song $song is created \n"; }
传递的参数存储在本地$song
变量中。
$ php constructor2.php Song Bad romance is created
现在我们有一条带有歌曲标题的消息打印到控制台。
在下一个示例中,我们初始化类的数据成员。初始化变量是构造函数的典型工作。
<?php class Friend { private $born; private $name; function __construct($name, $born) { $this->name = $name; $this->born = $born; } function getInfo() { echo "My friend $this->name was born in $this->born\n"; } } $friend = new Friend("Monika", 1990); $friend->getInfo();
我们有一个包含数据成员和方法的Friend
类。
private $born; private $name;
类定义中有两个变量。private
关键字是一个访问修饰符。它是一种封装形式。private
关键字是限制性最强的修饰符。它只允许相关对象访问变量。没有后代,没有其他对象。稍后会详细介绍此主题。
function __construct($name, $born) { $this->name = $name; $this->born = $born; }
在构造函数中,我们初始化两个数据成员。$this
变量是用于引用对象变量的处理程序。
$friend = new Friend("Monika", 1990); $friend->getInfo();
我们创建了一个带有两个参数的Friend对象。然后我们调用对象的getInfo
方法。要调用对象方法,我们使用->
运算符。
$ php friend.php My friend Monika was born in 1990
PHP类常量
PHP允许创建类常量。这些常量不属于具体对象。他们属于阶级。按照惯例,常量用大写字母书写。
<?php class Math { const PI = 3.14159265359; public function getPI() { echo self::PI; } } $math = new Math(); echo Math::PI, "\n"; echo $math->getPI(), "\n";
我们有一个带有PI常量的Math类。
const PI = 3.14159265359;
const
关键字用于定义常量。
public function getPI() { echo self::PI; }
使用self
关键字后跟两个冒号从方法内部访问类常量。
echo Math::PI, "\n"; echo $math->getPI(), "\n";
我们将PI
常量打印到控制台。在第一种情况下,我们通过引用类名、后跟两个冒号和一个常量名来获取常量值。请注意,不需要任何对象来获取类常量。在第二种情况下,我们使用对象方法。
PHPinstanceof关键字
instanceof
关键字用于判断一个PHP变量是否是某个类的实例化对象。
<?php class Cat {} class Dog {} class Bird {} $objects = [ new Cat(), new Dog(), new Cat(), new Bird(), new Bird(), new Dog(), new Dog(), new Cat(), new Bird() ]; shuffle($objects); foreach ($objects as $object) { if ($object instanceof Cat) { echo "It is a Cat\n"; } elseif ($object instanceof Dog) { echo "It is a Dog\n"; } else if ($object instanceof Bird) { echo "It is a Bird\n"; } }
在上面的脚本中,我们有三个类:Cat
、Dog
和Bird
。我们遍历数组并为每个数组值打印类。
$objects = [ new Cat(), new Dog(), new Cat(), new Bird(), new Bird(), new Dog(), new Dog(), new Cat(), new Bird() ];
我们创建了一个包含这些对象的数组。
shuffle($objects);
我们打乱数组。此时,我们不知道数组值的类类型。
if ($object instanceof Cat) { echo "It is a Cat\n"; }
这里我们使用instanceof
关键字来找出类的类型。
$ php instanceof.php It is a Bird It is a Cat It is a Cat It is a Dog It is a Dog It is a Cat It is a Dog It is a Bird It is a Bird
PHP__toString方法
当我们对对象实例使用print
或echo
关键字时,会调用__toString
特殊方法。我们将在以下示例。
<?php class Cat { public $name; public $age; function __construct($name, $age) { $this->age = $age; $this->name = $name; } function __toString() { return "Cat: $this->name, Age: $this->age \n"; } } $missy = new Cat("Missy", 6); $lucky = new Cat("Lucky", 4); print $missy; echo $lucky;
我们有一个Cat类,其中定义了一个__toString
特殊方法。
function __toString() { return "Cat: $this->name, Age: $this->age \n"; }
该方法打印对象的基本信息。
$missy = new Cat("Missy", 6); $lucky = new Cat("Lucky", 4);
我们创建了Cat类的两个对象。
print $missy; echo $lucky;
我们在它们上面使用了print
或echo
关键字。
$ php tostring.php Cat: Missy, Age: 6 Cat: Lucky, Age: 4
PHP继承
继承是一种使用已经定义的类来形成新类的方法。新形成的类称为派生类,我们从中派生的类称为基类。继承的重要好处是代码重用和降低程序的复杂性。派生类(后代)覆盖或扩展基类(祖先)的功能。
<?php class Base { function __construct() { echo "Construction of Base class \n"; } } class Derived extends Base { function __construct() { parent::__construct(); echo "Construction of Derived class \n"; } } $obj1 = new Base(); $obj2 = new Derived();
在这个PHP脚本中,我们有两个类:一个Base
类和一个Derived
类。Derived
类继承自Base
类。
class Derived extends Base {
在PHP中,我们使用extends
关键字来创建继承关系。
function __construct() { parent::__construct(); echo "Construction of Derived class \n"; }
在派生类的构造函数中,我们调用了父类的构造函数。我们使用parent
关键字,后跟两个冒号和__construct
方法。必须显式调用父类的构造函数。
$obj1 = new Base(); $obj2 = new Derived();
我们实例化了Base
和Derived
类。
$ php derived.php Construction of Base class Construction of Base class Construction of Derived class
下面是一个更复杂的例子。
<?php abstract class Being { protected $isAlive = true; public function isAlive() { if ($this->isAlive) { echo "Being is alive\n"; } else { echo "Being is not alive\n"; } } public function kill() { $this->isAlive = false; } } abstract class Animal extends Being { protected $age; public function __construct($age) { $this->age = $age; } protected function setAge($age) { $this->age = $age; } public function getAge() { return $this->age; } } class Cat extends Animal { private $name; public function __construct($name, $age) { $this->name = $name; parent::__construct($age); } public function getName() { return $this->name; } } $cat = new Cat("Cici", 4); $cat->isAlive(); echo $cat->getName() . " is " . $cat->getAge() . " years old\n"; $cat->kill(); $cat->isAlive();
我们在这里使用了几个新概念。在代码示例中,我们有三个类:Being
、Animal
和Cat
。Animal
类继承自Being
类。Cat
类继承自Animal
类。类继承未声明为私有的方法和数据成员。
abstract class Being {
Being
类被声明为抽象
。abstract
关键字禁止类的实例化。创建Being
类的实例没有多大意义。
protected $isAlive = true;
$isAlive
数据成员被声明为protected
。此类成员只能由定义它们的类及其后代访问。
abstract class Animal extends Being {
Animal
类也被声明为抽象类。它继承自类Being
。为此,我们使用extends
关键字。Animal
是后代。它继承基Being
类的方法和变量。
class Cat extends Animal {
Cat
类继承自Animal
类。它继承自Animal
类并间接继承自Being
类。它没有声明为抽象的,这意味着我们可以实例化它。
parent::__construct($age);
在Cat
类的构造函数中,我们使用parent
关键字调用父构造函数,后跟两个冒号和__construct
方法。必须显式调用父类的构造函数。
$cat = new Cat("Cici", 4); $cat->isAlive(); echo $cat->getName() . " is " . $cat->getAge() . " years old\n"; $cat->kill(); $cat->isAlive();
我们创建了一只新猫:Cici,4岁。然后我们在cici对象上调用函数。注意方法的用法不是在Cat
类中创建的,而是从父类继承的。
$ php inheritance.php Being is alive Cici is 4 years old Being is not alive
PHP抽象类和方法
不能实例化抽象类。如果一个类至少包含一个抽象方法,那么它也必须声明为抽象方法。抽象方法不能被实现,它们只是声明方法的签名。当我们继承一个抽象类时,所有的抽象方法都必须由派生类实现。此外,必须以相同或较少限制的可见性声明这些方法。
与接口不同,抽象类可能有完全实现的方法,也可能有定义的成员字段。所以抽象类可能会提供部分实现。程序员经常将一些通用功能放入抽象类中。这些抽象类后来被子类化以提供更具体的实现。例如,Qtgraphics库有一个QAbstractButton
,它是按钮小部件的抽象基类,提供按钮通用的功能。ButtonsQ3Button
、QCheckBox
、QPushButton
、QRadioButton
和QToolButton
继承自这个基本抽象类。
正式地说,抽象类用于执行协议。协议是所有实现对象都必须支持的一组操作。
<?php abstract class Drawing { protected $x = 0; protected $y = 0; public abstract function area(); public function getCoordinates() { echo "\$x is $this->x\n"; echo "\$y is $this->y\n"; } } class Circle extends Drawing { private $radius; public function __construct($x, $y, $r) { $this->radius = $r; $this->x = $x; $this->y = $y; } public function area() { return $this->radius * $this->radius * pi(); } public function __toString() { return "Circle, at x: $this->x, y: $this->y, radius: $this->radius"; } } $o = new Circle(12, 45, 22); echo "$o \n"; echo "Area of the circle: " . $o->area() . "\n"; echo $o->getCoordinates();
在我们的PHP脚本中,我们有一个抽象基Drawing
类。该类定义了两个成员字段,定义了一个方法并声明了一个方法。其中一个方法是抽象的,另一个是完全实现的。Drawing
类是抽象的,因为我们无法绘制它。我们可以画一个圆、一个点或一个正方形。Drawing
类对我们可以绘制的对象具有一些通用功能。
class Circle extends Drawing {
Circle
是Drawing
类的子类。它必须实现抽象区域方法。
$ php abstract.php Circle, at x: 12, y: 45, radius: 22 Area of the circle: 1520.53084434 $x is 12 $y is 45
PHP接口
遥控器是观众和电视之间的接口。它是此电子设备的接口。外交礼仪指导外交领域的一切活动。道路规则是驾车者、骑自行车者和行人必须遵守的规则。编程中的接口类似于前面的示例。
接口是:
- API
- 合同
对象通过它们公开的方法与外界交互。实际的实现对程序员来说并不重要,或者它也可能是秘密的。一家公司可能会出售一个图书馆,但它不想透露实际的实施情况。程序员可能会在GUI工具包的窗口上调用maximize
方法,但对这个方法是如何实现的一无所知。从这个角度来看,接口是对象与外界交互的方法,而不暴露太多关于他们的内部运作。
从第二个角度来看,接口就是契约。如果达成一致,则必须遵守。它们用于设计应用程序的架构,并帮助组织代码。
接口是完全抽象的类型。它们是使用interface
关键字声明的。接口只能有方法签名和常量。接口中声明的所有方法签名都必须是公开的。它们不能有完全实现的方法,也不能有成员字段。一个PHP类可以实现任意数量的接口。一个接口还可以扩展任意数量的接口。实现接口的类必须实现接口的所有方法签名。
接口用于模拟多重继承。一个PHP类只能扩展一个类。一个PHP类可以实现多个接口。使用接口的多重继承不是关于继承方法和变量。它是关于继承由接口描述的思想或契约。
接口和抽象类之间有一个重要区别。抽象类为继承层次结构中相关的类提供部分实现。另一方面,接口可以由彼此不相关的类实现。例如,我们有两个按钮:一个经典按钮和一个圆形按钮。两者都继承自为所有按钮提供一些通用功能的抽象按钮类。实现类是相关的,因为它们都是按钮。另一个例子可能有类Database
和SignIn
。他们彼此没有关系。我们可以应用一个ILoggable
接口,强制他们创建一个方法来进行日志记录。
<?php interface IInfo { public function do_inform(); } class Some implements IInfo { public function do_inform() { echo "This is a Some class\n"; } } $sm = new Some(); $sm->do_inform();
这是一个演示界面的简单PHP脚本。
interface IInfo { public function do_inform(); }
这是一个接口IInfo
。它具有do_inform
方法签名。
class Some implements IInfo {
我们使用implements
从接口中实现。
public function do_inform() { echo "This is a Some class\n"; }
该类提供了do_inform
方法的实现。
下一个示例展示了一个类如何实现多个接口。
<?php interface Device { public function switch_on(); public function switch_off(); } interface Volume { public function volume_up(); public function volume_down(); } interface Pluggable { public function plug_in(); public function plug_off(); } class CellPhone implements Device, Volume, Pluggable { public function switch_on() { echo "Switching on\n"; } public function switch_off() { echo "Switching off\n"; } public function volume_up() { echo "Volume up\n"; } public function volume_down() { echo "Volume down\n"; } public function plug_in() { echo "Plugging in\n"; } public function plug_off() { echo "Plugging off\n"; } } $o = new CellPhone(); $o->switch_on(); $o->volume_up(); $o->plug_in();
我们有一个继承自三个接口的CellPhone类。
class CellPhone implements Device, Volume, Pluggable {
该类实现了所有三个接口,以逗号分隔。CellPhone
类必须实现所有三个接口的所有方法签名。
$ php interface.php Switching on Volume up Plugging in
下一个示例展示了接口如何从多个其他接口扩展。
<?php interface IInfo { public function do_inform(); } interface IVersion { public function get_version(); } interface ILog extends IInfo, IVersion { public function do_log(); } class DBConnect implements ILog { public function do_inform() { echo "This is a DBConnect class\n"; } public function get_version() { echo "Version 1.02\n"; } public function do_log() { echo "Logging\n"; } public function connect() { echo "Connecting to the database\n"; } } $db = new DBConnect(); $db->do_inform(); $db->get_version(); $db->do_log(); $db->connect();
在这个PHP脚本中,我们定义了三个接口。扩展接口允许我们组织它们。
interface ILog extends IInfo, IVersion { public function do_log(); }
ILog接口扩展了其他两个接口。
public function do_inform() { echo "This is a DBConnect class\n"; }
DBConnect
类实现了do_inform
方法。该方法被类实现的ILog
接口继承。
PHP多态性
多态性是针对不同的数据输入以不同的方式使用运算符或函数的过程。实际上,多态性意味着如果Bin类继承自A类,则它不必继承A类的所有内容;它可以做一些A类做的不同的事情。
一般来说,多态性是以不同形式出现的能力。从技术上讲,它是为派生类重新定义方法的能力。多态性涉及将特定实现应用于接口或更通用的基类。
<?php abstract class Shape { private $x = 0; private $y = 0; public abstract function area(); } class Rectangle extends Shape { function __construct($x, $y) { $this->x = $x; $this->y = $y; } function area() { return $this->x * $this->y; } } class Square extends Shape { function __construct($x) { $this->x = $x; } function area() { return $this->x * $this->x; } } $shapes = [ new Square(5), new Rectangle(12, 4), new Square(8) ]; foreach ($shapes as $shape) { echo $shape->area() . "\n"; }
在上面的PHP脚本中,我们有一个抽象的Shape
类。这个类变形为两个后代类:Rectangle
和Square
。它们都提供了它们自己的area
方法实现。多态性为OOP带来了灵活性和可扩展性系统。
在本文中,我们介绍了PHP中的OOP。
列出所有PHP教程。