PHP Day5:面向对象
笔者回顾对面向对象的理解
- 封装:类是将对象进行封装,把对象的属性和操作(或服务)结合为一个独立的整体,并尽可能隐藏对象的内部实现细节。
- 继承:单继承,多实现。 有继承,就会有父子关系。子类拥有父类所有的方法和属性。
- 抽象:一切皆对象,一切都可以进行抽象。抽象就是忽略内部的方法和实现,只关心和自己需求相关的部分。抽象包括过程抽象和数据抽象。重点关注和需求相关的,忽略和需求无关的,集中注意力在需求上。
- 多态:经典的实现是重载和重写。笔者有一段时间理解实现接口和继承父类也是多态性的体现(可能不太准确)
- 面向对象的思想需要和实际项目结合,绝对不是纸上谈兵。这种思想不仅适用于我们编写代码开发项目,更适用于我们采用什么样的方法和思路去敏捷开发,调优升级。
对象的三大特征
- 对象的行为:可以给对象添加哪些操作:比如开门,关门。就是对门这个对象的行为;
- 对象的形态:给对象施加方法时,对象的外形如何变化:门的颜色、尺寸、材料;
- 对象的表示:就是对象的唯一标识(身份证),最大的意义是区分在相同行为和形态下有什么不同。
面向对象的内容
- 类:定义了一件事物的抽象特点。类的定义包含了数据的形式(常量、变量)以及对数据的操作(函数)。
- 对象:是类的实例。
- 成员变量:定义在类内部的变量。该变量的值对外是不可见的,可以通过成员函数访问。在类被实例化为对象之后,该变量即可称为变量的属性。
- 成员函数:定义在类的内部,可用于访问对象的数据。
- 父类:一个类被其他类继承,可将该类称为父类,基类或超类。
- 子类:一个类继承其他类,可将该类称为子类或派生类。
- 继承:继承是子类自动共享父类数据结构和方法的一种机制,这是类之间的一种关系。在定义和实现一个类的时候,可以在一个已经存在的类的基础之上进行,将这个已经存在的类所定义的内容作为自己的内容,并添加若干新内容。这就是继承非常好理解,关键字extends。
- 多态: 多态性是指相同的操作或函数、过程可作用于多种类型的对象上并获得不同的结果。通俗的解释一下:不同的对象,收到同一消息可以产生不同的结果,这种现象就是多态。(反思一下自己之前对接口实现和类继承的理解也是说的通的)
- 重载:简单讲就是在同一个类中,方法名相同,但是参数列表不同(参数个数,参数属性....)。像这种同名不同参的函数或者方法之间,互相称为重载关系。
- 抽象性:是指将具有一致数据结构(属性)和行为(操作)的对象抽象成类。一个类就是这样一种抽象,其核心是:反应了与应用(需求)密切相关的信息,忽略那些无关紧要的内容。在项目中,任何类的划分都是主观的,但是必须与具体的应用需求相关。
- 封装:封装是指将现实世界中存在的某个客体的属性和行为绑定在一起,并放置在一个逻辑单元内。
- 构造函数:主要用来在创建对象时初始化对象, 即为成员变量赋值初始化。构造函数在创建对象的语句中总与new关键字一起使用。
- 析构函数(destructor)与构造函数相反, 当对象结束其生命周期时,系统自动执行析构函数。析构函数往往做的是‘清理善后’的工作。
- 延伸之析构函数详解
构造函数与析构函数
构造函数
function __construct(){
parent::__construct(); // 调用父类的构造函数必须显示的使用parent调用父类构造函数
classname::__construct(); // 调用其他类的构造函数,classname是类名
//其他操作
}
如果涉及多层继承,当调用parent::__construct()时,会沿着父类向上搜索,直到找到最合适的构造函数,例如:
// 接上例
class Parrot extends Birds{
private $name;
private $leg;
private $wing;
function __construct($name){
parent::__construct($name); // 此时没有找到父类(Birds类)合适的构造函数,只能向上搜索,搜索到Animal类时,才找到合适的构造函数
echo "鹦鹉类被创建!";
$this->smackTalk();
/*
输出结果:
"动物类被创建!"
"鹦鹉说话!"
*/
}
function smackTalk(){
echo "鹦鹉说话!";
}
}
如果想要依次调用几个父类的构造函数,可以使用类名直接调用构造函数,例如:
function __construct($name,$leg){
Animal::__construct($name); // 调用Animal构造函数
Birds::__construct($name,$leg); // 调用Birds构造函数
}
析构函数
- 析构函数是在销毁对象时,自动调用,不能显示的调用。
- 析构函数不能带参数。
- 在以下几种情况下可能会调用析构函数(但不一定):
- PHP页面加载完毕之后;
- unset()类;
- 变量引用指向别的对象或值时;
<?php
class test{
function __destruct(){
echo "当对象销毁时会调用!!!";
}
}
$a = $b = $c = new test();
$a = null;
unset($b);
echo "<hr />";
?>
此例子,如下图,有三个变量引用$a,$b,$c指向test对象,test对象就有3个引用计数,当$a = null时,$a对test对象的引用丢失,计数-1,变为2,当$b被unset()时,$b对test对象的引用也丢失了,计数再-1,变为1,最后页面加载完毕,$c指向test对象的引用自动被释放,此时计数再-1,变为0,test对象已没有变量引用,就会被销毁,此时就会调用析构函数。
析构函数
PHP构造函数 __construct
void __construct ([ mixed $args [, $... ]] )
function __construct( $par1, $par2 ) {
$this->url = $par1;
$this->title = $par2;
}
PHP析构函数 __destruct
<?php
class MyDestructableClass {
function __construct() {
print "构造函数\n";
$this->name = "MyDestructableClass";
}
function __destruct() {
print "销毁 " . $this->name . "\n";
}
}
$obj = new MyDestructableClass();
?>
执行上述代码,返回结果是:
构造函数
销毁 MyDestructableClass
PHP重写和重载
- PHP不支持像Java那种强类型语言一样的重载。
PHP重写
如果从父类继承的方法不能满足子类的需求,可以对其进行改写,这个过程叫方法的覆盖(override),也称为方法的重写。
实例中重写了 getUrl 与 getTitle 方法:
function getUrl() {
echo $this->url . PHP_EOL;
return $this->url;
}
function getTitle(){
echo $this->title . PHP_EOL;
return $this->title;
}
访问控制
PHP对属性或者方法的访问控制,是通过在前面添加关键字实现的:
- public(共有的):公有的类成员可以在任何地方被访问。
- protected(受保护):受保护的类成员可以在自身或者子父类中被访问。
- private(私有):私有的类成员则只能被其定义所在的类访问。
属性的访问控制
类属性必须定义为公有,受保护,私有之一。如果用 var 定义,则被视为公有。
<?php
/**
* Define MyClass
*/
class MyClass
{
public $public = 'Public';
protected $protected = 'Protected';
private $private = 'Private';
function printHello()
{
echo $this->public;
echo $this->protected;
echo $this->private;
}
}
$obj = new MyClass();
echo $obj->public; // 这行能被正常执行
echo $obj->protected; // 这行会产生一个致命错误
echo $obj->private; // 这行也会产生一个致命错误
$obj->printHello(); // 输出 Public、Protected 和 Private
/**
* Define MyClass2
*/
class MyClass2 extends MyClass
{
// 可以对 public 和 protected 进行重定义,但 private 而不能
protected $protected = 'Protected2';
function printHello()
{
echo $this->public;
echo $this->protected;
echo $this->private;
}
}
$obj2 = new MyClass2();
echo $obj2->public; // 这行能被正常执行
echo $obj2->private; // 未定义 private
echo $obj2->protected; // 这行会产生一个致命错误
$obj2->printHello(); // 输出 Public、Protected2 和 Undefined
?>
方法的访问控制
类中的方法可以被定义为公有,私有或受保护。如果没有设置这些关键字,则该方法默认为公有。
<?php
/**
* Define MyClass
*/
class MyClass
{
// 声明一个公有的构造函数
public function __construct() { }
// 声明一个公有的方法
public function MyPublic() { }
// 声明一个受保护的方法
protected function MyProtected() { }
// 声明一个私有的方法
private function MyPrivate() { }
// 此方法为公有
function Foo()
{
$this->MyPublic();
$this->MyProtected();
$this->MyPrivate();
}
}
$myclass = new MyClass;
$myclass->MyPublic(); // 这行能被正常执行
$myclass->MyProtected(); // 这行会产生一个致命错误
$myclass->MyPrivate(); // 这行会产生一个致命错误
$myclass->Foo(); // 公有,受保护,私有都可以执行
/**
* Define MyClass2
*/
class MyClass2 extends MyClass
{
// 此方法为公有
function Foo2()
{
$this->MyPublic();
$this->MyProtected();
$this->MyPrivate(); // 这行会产生一个致命错误
}
}
$myclass2 = new MyClass2;
$myclass2->MyPublic(); // 这行能被正常执行
$myclass2->Foo2(); // 公有的和受保护的都可执行,但私有的不行
class Bar
{
public function test() {
$this->testPrivate();
$this->testPublic();
}
public function testPublic() {
echo "Bar::testPublic\n";
}
private function testPrivate() {
echo "Bar::testPrivate\n";
}
}
class Foo extends Bar
{
public function testPublic() {
echo "Foo::testPublic\n";
}
private function testPrivate() {
echo "Foo::testPrivate\n";
}
}
$myFoo = new foo();
$myFoo->test(); // Bar::testPrivate
// Foo::testPublic
?>
接口
- 使用接口,可以指定某个类必须实现哪些方法,但不需要定义这些方法的具体内容。(即只声明,不实现)
- 接口是通过interface关键字来定义的,就像定义一个标准的类一样, 但其中定义的所有的方法都是空的。
- 接口的特性是:接口中定义的所有方法必须都是公共的。
- 要实现一个接口,使用implements操作符。类中必须实现接口中定义的所有方法,否则会报错。类可以实现多个接口(中间用逗号隔开),但是只能继承一个父类。(这点和Java是一样的)
<?php
// 声明一个'iTemplate'接口
interface iTemplate
{
public function setVariable($name, $var);
public function getHtml($template);
}
// 实现接口
class Template implements iTemplate
{
private $vars = array();
public function setVariable($name, $var)
{
$this->vars[$name] = $var;
}
public function getHtml($template)
{
foreach($this->vars as $name => $value) {
$template = str_replace('{' . $name . '}', $value, $template);
}
return $template;
}
}
常量
- 把在类中始终保持不变的值定义为常量。注意:在定义和使用常量的时候不需要加$字符。
- 常量的值必须是一个定值,不能是变量,类属性,数学运算的结果或函数调用。
- 自php5.3开始,可以用一个变量来动态的调用类。但该变量的值不能为关键字(self,parent,static)
<?php
class MyClass
{
const constant = '常量值';
function showConstant() {
echo self::constant . PHP_EOL;
}
}
echo MyClass::constant . PHP_EOL;
$classname = "MyClass";
echo $classname::constant . PHP_EOL; // 自 5.3.0 起
$class = new MyClass();
$class->showConstant();
echo $class::constant . PHP_EOL; // 自 PHP 5.3.0 起
?>
抽象类
- 任何一个类,如果他里面至少有一个方法被声明为抽象的,那么这个类就必须是抽象类。
- 抽象类不能被实例化。(实例化由其实现类来做)。
- 抽象方法只声明了其调用方式(参数),不能定义其具体的功能实现。(是不是想到了接口)
- 继承抽象类时,子类必须定义父类所有的抽象方法。这些方法的访问控制(权限)必须和父类一致,或者更为宽松。
- 例如某个抽象方法被声明为受保护的,那么子类中实现的方法就应该声明为受保护的或者公有的,而不能定义为私有的。
- 此外方法的调用方式必须匹配,即类型和所需参数数量必须一致。
- 例如,子类定义了一个可选参数,而父类抽象方法的声明里没有,则两者的声明并无冲突。
<?php
abstract class AbstractClass
{
// 强制要求子类定义这些方法
abstract protected function getValue();
abstract protected function prefixValue($prefix);
// 普通方法(非抽象方法)
public function printOut() {
print $this->getValue() . PHP_EOL;
}
}
class ConcreteClass1 extends AbstractClass
{
protected function getValue() {
return "ConcreteClass1";
}
public function prefixValue($prefix) {
return "{$prefix}ConcreteClass1";
}
}
class ConcreteClass2 extends AbstractClass
{
public function getValue() {
return "ConcreteClass2";
}
public function prefixValue($prefix) {
return "{$prefix}ConcreteClass2";
}
}
$class1 = new ConcreteClass1;
$class1->printOut();
echo $class1->prefixValue('FOO_') . PHP_EOL;
$class2 = new ConcreteClass2;
$class2->printOut();
echo $class2->prefixValue('FOO_') . PHP_EOL;
?>
以上代码的执行结果:
ConcreteClass1
FOO_ConcreteClass1
ConcreteClass2
FOO_ConcreteClass2
Static关键字
- 声明类属性或方法为static,就可以不实例化而直接访问了。(使用要有技巧,否则会内存溢出)
- 静态属性不能通过一个类已实例化的对象来访问(但静态方法可以)
- 由于静态方法不需要通过对象就可以调用,所以伪对象$this在静态方法中不可用。
- 静态属性不可以由对象通过->操作符来访问。
- 自 PHP 5.3.0 起,可以用一个变量来动态调用类。但该变量的值不能为关键字 self,parent 或 static。
<?php
class Foo {
public static $my_static = 'foo';
public function staticValue() {
return self::$my_static;
}
}
print Foo::$my_static . PHP_EOL;
$foo = new Foo();
print $foo->staticValue() . PHP_EOL;
?>
执行以上程序,输出结果为:
foo
foo
Final关键字
PHP 5 新增了一个 final 关键字。如果父类中的方法被声明为 final,则子类无法覆盖该方法。如果一个类被声明为 final,则不能被继承。
<?php
class BaseClass {
public function test() {
echo "BaseClass::test() called" . PHP_EOL;
}
final public function moreTesting() {
echo "BaseClass::moreTesting() called" . PHP_EOL;
}
}
class ChildClass extends BaseClass {
public function moreTesting() {
echo "ChildClass::moreTesting() called" . PHP_EOL;
}
}
// 报错信息 Fatal error: Cannot override final method BaseClass::moreTesting()
?>
调用父类构造方法
PHP 不会在子类的构造方法中自动的调用父类的构造方法。要执行父类的构造方法,需要在子类的构造方法中调用 parent::__construct() 。
<?php
class BaseClass {
function __construct() {
print "BaseClass 类中构造方法" . PHP_EOL;
}
}
class SubClass extends BaseClass {
function __construct() {
parent::__construct(); // 子类构造方法不能自动调用父类的构造方法
print "SubClass 类中构造方法" . PHP_EOL;
}
}
class OtherSubClass extends BaseClass {
// 继承 BaseClass 的构造方法
}
// 调用 BaseClass 构造方法
$obj = new BaseClass();
// 调用 BaseClass、SubClass 构造方法
$obj = new SubClass();
// 调用 BaseClass 构造方法
$obj = new OtherSubClass();
?>
执行以上程序,输出结果为:
BaseClass 类中构造方法
BaseClass 类中构造方法
SubClass 类中构造方法
BaseClass 类中构造方法