一、 Introduction
面向对象编程,OOP,Object Oriented Program,是一种编程思想,很多高级语言具有这个特征,比如java,c++,python等。Php也具有面向对象的特性。与面向对象编程,经常类比的是,面向过程编程。面向过程的基本特征是,将要完成的任务,分割为若干个步骤,比如,第1步做什么,第2步做什么,…最后,任务完成。面向对象的基本特征是,将要完成的任务,分派给不同的对象去做,比如,某对象1做什么,某对象2做什么,…程序一旦启动,各个对象就相互配合完成任务。
二、 Php面向对象的基本概念
1. 类和对象
这个基本概念不单是php面向对象的特征,是普遍存在于面向对象的编程语言中的。类比现实世界,现实世界由很多不同的事物组成,万物都是对象,特征相似或相近的事物,可以归结为同类,比如猫和狗都是动物类,在教室学习的归为学生类,教学生的归为教师类,教师和学生都是人类,等等。高度归纳概括总结后,面向对象的基本概念可以看作是类和对象。
2. 定义一个类和创建类的对象
例子:
<?php
class persion{
var $name;
var $age;
var $edu;
}
$obj1 = new Persion();
var_dump($obj1);
$obj1 ->name = ‘哈哈’;
$obj1 ->age = 19;
$obj1 ->edu = ‘小学’;
var_dump($obj1);
$name1 = $obj1 ->name;
echo "<br />" ."$name1";
?>
总结,类是描述一个事物的总称,是具有相同特征的该类的一个通用名称;对象是一个明确的具体的物体,是某个类中的一个实物。
3. 类的属性和方法
类中定义的变量是这个类的属性,类中定义的函数是这个类的方法。
比如,
<?php
class Person{
var $name = '哈哈';
var $age = 16;
var $edu = '小学';
function hahaxiao($n, $a,$e){
echo "<br/>{$a}岁{$n}学历{$e}在哈哈哈";
}
function chifan(){
echo "<br/>{$this ->age}岁{$this ->name}学历{$this->edu}在吃饭";
}
}
$obj1 = new Person();
$obj1 ->hahaxiao('hah', 15, '小学');
$obj1 ->chifan();
?>
4. 创建对象的几种方式
使用new 类名();
$obj1 = newPerson();
使用new 对象名();,这种方式是通过一个对象,new出一个对象,其实是new出的旧对象所属类的一个新对象。
$obj1 = newPerson();
$obj2 = new $obj1();
使用字符串变量,这种方式是利用了可变类,就是类的名字是一个变量。
class Person{
var $name = '哈哈';
var $age = 16;
var $edu = '小学';
function hahaxiao($n, $a,$e){
echo "<br/>{$a}岁{$n}学历{$e}在哈哈哈";
}
function chifan(){
echo "<br/>{$this ->age}岁{$this ->name}学历{$this->edu}在吃饭";
}
}
$s1 = "Person";
$s2 = new $s1();
echo "<br>" ;
var_dump($s2);
使用self,表示当前类本身,只能用在一个类的方法中
class Person{
var $name = '哈哈';
var $age = 16;
var $edu = '小学';
function hahaxiao($n, $a,$e){
echo "<br/>{$a}岁{$n}学历{$e}在哈哈哈";
}
function chifan(){
$s = new self();
echo "<br/>{$this ->age}岁{$this ->name}学历{$this->edu}在吃饭";
var_dump($s);
}
}
5. 对象的传值方式
对于对象,值传递和引用传递,结果没有区别,因为对象变量存储的数据只是一个对象编号,这个对象编号指向对象数据,值传递实际上是复制的这个对象编号,这个复制的对象编号仍然指向对象数据。引用传递实际上是直接指向引用的对象变量的对象编号。数据在内存或者磁盘中存储以地址的方式呈现,可以认为值传递和引用传递的数据地址不变,所以结果几乎没有区别。例子,
<?php
class A1{
var $p1 = 1;
}
$v1 = 1;
$v2 = $v1; //值传递
$v1++;
echo "<br />v1 = $v1,v2 = $v2";
$v1 = 1;
$v2 = & $v1; //引用传递
$v1++;
echo "<br />v1 = $v1,v2 = $v2";
$o1 = new A1();
$o2 = $o1; //值传递
$o1 ->p1 = 3;
echo "<br />o1 ->p1 = {$o1 ->p1},o2 ->p1 = {$o2->p1}";
$o3 = new A1();
$o4 = & $o3; //引用传递
$o3 ->p1 = 3;
echo "<br />o3 ->p1 = {$o3 ->p1},o4 ->p1 = {$o4->p1}";
?>
三、 类中的成员
类中的成员分为3大类:属性、方法、常量。
类的一般形式,如下,
class 类名{
常量定义1;
常量定义2;
……
属性定义1;
属性定义2;
……
方法定义1;
方法定义2;
……
}
详细的分,类的成员可以有:
属性:
普通属性;
静态属性;
方法:
普通方法;
静态方法;
构造方法;
析构方法;
常量:
1. 常量
在类中定义的常量,称为类常量,实际上就是一个常量。
形式,
class 类名{
const 常量名 = 常量值;
//注意不可以使用define();来定义。
}
例子,
<?php
class A1{
const PI = 3.14;
const A = 10;
}
$v1 = A1::PI*2*2;
echo "<br />v1 =$v1";
echo "<br />A1::A = " . A1::A;
?>
2. 属性
1) 普通属性
普通属性,又叫做实例属性,实例就是对象。
定义形式
class 类名{
var $属性名 = 初始值;
var $属性名;
//var可以使用public代替
public $属性名 = 初始值;
public $属性名
}
使用形式
$对象->属性名;
$v1 = $对象->属性名;
echo $对象 ->属性名;
$v2 = $对象->属性名* 3 + 5;
2) 静态属性
静态属性,本质上也是变量,但其有一个特点就是,该变量只隶属于类,一个类中的一个静态属性,就只有一份数据。相比,一个类中的实例属性,可以有多份数据,每创建一个对象出来,就会有一个数据。静态属性被对象所共享。
定义形式
class 类名{
static public $属性名 = 初始值;
static public $属性名
}
使用形式
类名::$静态属性名;
对象常量使用是:类名::常量名;
例子,
class Person{
public $name = 'hh';
static $count = 0;
}
$p1 = new Person();
$p1 ->name = '哈哈';
Person::$count++;
$p2 = new Person();
$p2 ->name = '嘿嘿';
Person::$count++;
echo "<br />人数为:" . Person::$count;
3. 方法
1) 普通方法
也叫实例方法,可以为这个类中的所有对象调用的方法。
定义形式
class 类名{
function 方法名(形参1, 形参2, 形参3, ……){
//方法体…
}
}
调用形式
$对象名 ->方法名(实参1,实参2,……);
2) 静态方法
一个类中定义的方法,只隶属于这个类。
定义形式
class 类名{
static function 方法名(形参1, 形参2, 形参3, ……){
//方法体…
}
}
调用形式
类名::方法名(实参1,实参2,……);
实例方法和静态方法的例子:
<?php
class A1{
public $p1 = 1;
static $p2 = 2;
function showInfo1(){
echo "<br />实例方法1";
echo "<br />p1的值为:" . $this ->p1;
//实例方法中可以使用静态属性
echo "<br />p2的值为:" . self::$p2;
}
static function showInfo2(){
echo "<br />静态方法1";
//echo "<br />p1的值为:" . $this ->p1;//错误的使用
echo "<br />p2的值为:" . self::$p2;//self代表类。不可使用this,this代表所在类的对象。
}
}
$o1 = new A1();
$o1 ->showInfo1();
$o1::showInfo2();
?>
注意:静态方法中不可以使用非静态的属性。因为静态的加载先于非静态的。
3) 构造方法
一个特殊的方法,名字是固定的,_ _construct;,该方法通常不是我们自己调用,而是在new一个对象的时候被调用,该方法的主要目的是在new一个对象的时候,给该方法设置一些初始值。
例子
class B2{
public $name;
public $age;
public $edu;
function __construct($p1, $p2, $p3){
$this->name =$p1;
$this->age =$p2;
$this->edu =$p3;
}
function haha(){
echo"<br />{$this ->age}--{$this ->name}--{$this ->edu}哈哈";
}
}
$o1 = new B2('哈哈',12,'小学');
$o1 ->haha();
4) 析构方法
一个特殊的方法,名字为固定的词,__destruct。是在一个对象被销毁的时候会自动调用的方法,外界无法调用它。析构方法不能带参数,但方法中可以使用$this,代表当前对象。
例子
class C1{
public $name;
function __construct($n){
$this ->name = $n;
}
function __destruct(){
echo "<br />{$this ->name}被销毁了";
}
}
$o2 = new C1("haha");
一般,程序运行在内存中,程序运行结束,变量被销毁。
4. 关于对象的销毁时机
1) 如果程序结束,所有变量都会被销毁,变量所代表的对象也会被销毁。对象被销毁的顺序,在默认情况下,跟其创建的顺序相反。
2) 当一个对象没有任何变量指向它的时候,即使程序还没有结束,也会被销毁。
比如:
$o2 = new C1("haha");
$o3 = new C1("hei");
$o4 = new C1("hii");
unset($o2);//断开变量$o2跟其变量编号的数据
echo "<br />程序结束";
上例与这个例子比较
$o2 = new C1("haha");
$o3 = new C1("hei");
$o4 = new C1("hii");
unset($o2);//断开变量$o2跟其变量编号的数据
$o21 = $o3;
unset($o3);//虽然使用了unset,但是在程序结束前,对象o3没有被销毁,因为对象o21中还存储了o3的指向,程序结束时会先销毁o21,再销毁o4。
echo "<br />程序结束";
在对象销毁方面,引用传递与值传递的情况类似。
比如
$o31 = & $o3;
unset($o3);
但是如果引用传递的引用源指向一个普通的数据,由于普通数据不能指向地址,所以,引用源以及同样指向这个普通数据的引用传递变量与对象的指向断开,此时由于没有变量指向这个对象,对象销毁。
比如
$o2 = new C1("haha");
$o3 = new C1("hei");
$o4 = new C1("hii");
unset($o2);//断开变量$o2跟其变量编号的数据
$o21 = $o3;
unset($o3);//虽然使用了unset,但是在程序结束前,对象o3没有被销毁,因为对象o21中还存储了o3的指向
$o41 = & $o4;
$o4 = 4;//变量指向普通数据,断开与对象的指向,对象销毁。
echo "<br />程序结束";
四、 类的继承
1. 含义、语法和概念
将一个类A中的特性信息,传递到另一个类B中,此时就称为,B继承A,A派生出B。使用extends关键字。比如:
<?php
class A1{
public $p1 = "这是A的";
function f1(){
echo "<br/>这是A的方法";
}
}
class B extends A1{
public $p2 = "这是B的";
}
$b1 = new B();
echo $b1 ->p1;
$b1 ->f1();
?>
继承的类中已有的类称为父类,继承人家特性的是子类。Php的继承是单继承的,就是一个类只能从一个上级类继承其特性信息。大多数面向对象语言都是单继承的。C++是多继承的。在子类中可以再来定义自己的一些新的特有的特性信息,这称为扩展。
2. 访问控制修饰符
控制访问权限的修饰符,php中有3个,如下,
public,公共的,在所有位置都可以访问;
protected,受保护的,只能在该类内部和该类的子类或父类中访问;
private,私有的,只能在该类内部访问。
形式
class 类名{
访问控制修饰符 属性或方法定义;
}
一般,对于类的访问,分为3个访问位置,某个类的内部,某个类的继承类的内部,某个类的外部。
比如
class B1{
public $p1 = 1;
protected $p2 = 2;
private $p3 = 3;
}
$b = new B1();
echo "<br />p1=" . $b ->p1;
//echo "<br />p2=" . $b ->p2;//错误的用法,访问不到
//echo "<br />p3=" . $b ->p3;//错误的用法,访问不到
class B1{
public $p1 = 1;
protected $p2 = 2;
private $p3 = 3;
function showInfo(){
echo "<br/>p1=" . $this ->p1;
echo "<br/>p2=" . $this ->p2;
echo "<br/>p3=" . $this ->p3;
}
}
$b = new B1();
$b ->showInfo();
echo "<br />p1=" . $b ->p1;
3. 关键字parent
在面向对象编程中,parent代表父类。
使用方式:
parent::$属性或者方法; //通常是静态的属性或者方法,有时候可能是实例属性或者实例方法。
class C2{
static $p1 = 1;
static protected $p2 = 2;
}
class B2 extends C2{
static function show1(){
echo "<br />这是B子类的方法";
echo "<br />这是父类的属性p1:" . parent::$p1;
echo "<br />这是父类的属性p2:" . C2::$p2;
}
}
B2::show1();
class D{
public $p1 = 2;
function showInfo(){
echo "<br/>D中的属性p1:" . $this ->p1;
}
}
class E extends D{
function show2(){
echo "<p>调用父类的实例方法</p>" ;
parent::showInfo();
}
}
$e = new E();
$e ->show2();
比较self、this、parent,如表:
关键字 | 含义 | 使用位置 | 使用示例 |
parent | 代表父类(这个类) | 必须在一个方法中 | parent::属性或方法 |
self | 当前所在的类 | 同上 | self::静态属性或方法 |
$this | 调用当前方法的对象 | 同上 | $this ->实例属性或方法 |
4. 构造方法和析构方法中调用上级同类方法
如果一个类有构造方法,则实例化这个类的时候,就不会调用该父类的构造方法(如果有);
如果一个类没有构造方法,则实例化这个类的时候,就会自动调用父类的构造方法(如果有);
<?php
class A1{
public $p1 = "这是A的";
function __construct(){
echo "<br/>父类A的构造方法";
}
}
class B extends A1{
function__construct(){
echo"<br />子类B的构造方法";
}
}
class C extends A1{
//没有构造方法
}
$o1 = new B();
$o2 = new C();
?>
如果一个类有析构方法,则销毁这个类的时候,就不会调用父类的析构方法(如果有);
如果一个类没有析构方法,则销毁这个类的时候,就会自动调用父类的析构方法(如果有);
如果一个类有构造方法或析构方法,则可以去手动调用父类的同类方法(如果有);
手动调用的语法形式总是这样:
parent::构造方法或析构方法();
<?php
class Member{
public $name ="haha";
public $age = 12;
public $sex;
function __construct($name,$age, $sex){
$this->name = $name;
$this->age = $age;
$this->sex = $sex;
}
function showInfo(){
echo"<br />姓名:{$this ->name}";
echo"<br />age:{$this ->age}";
echo"<br />sex:{$this ->sex}";
}
}
class Teacher extends Member{
public $edu = "小学";
public $major;
function__construct($name, $age, $sex, $edu, $major){
//$this->name = $name;
//$this->age = $age;
//$this->sex = $sex;
parent::__construct($name,$age, $sex);
$this->edu = $edu;
$this ->major= $major;
}
function showInfo(){
echo"<br />姓名:{$this ->name}";
echo"<br />age:{$this ->age}";
echo"<br />sex:{$this ->sex}";
echo"<br />edu:{$this ->edu}";
echo"<br />major:{$this ->major}";
}
}
$t1 = new Teacher("haha",23,'男','初中','java');
$t1 ->showInfo();
?>
5. 重写override
重写,又叫覆盖,将一个类从父类中继承的属性和方法重新定义,相当于不要父类的某属性或方法,而是自己重新定义。
<?php
class Animal{
public $p1 = "吃饭";
function Move(){
echo "<br/>动动爪子";
}
}
class Fish extends Animal{
public $p1 = "吃泥鳅";
function Move(){
echo "<br/>动动尾巴";
}
}
class Bird extends Animal{
public $p1 = "吃虫子";
function Move(){
echo "<br/>动动翅膀";
}
}
$f = new Fish();
echo "<br />" . $f ->p1;
$f ->Move();
$b = new Bird();
echo "<br />" . $b ->p1;
$b ->Move();
?>
重写的基本要求:
访问控制权限上,子类覆盖的属性或方法的访问控制权限,不能低于父类的被覆盖的属性或方法的访问控制权限,比如,
父类:public 子类:只能是public;父类:protected 子类:可以是protected和public;父类:private 子类:不能覆盖。父类的私有成员,不能被子类覆盖。
方法的参数形式,子类覆盖的同名方法的时候,参数要求跟父类保持一致。特例,构造方法重写的时候参数可以不一致。
注意,虽然父类的私有属性不能被覆盖,但子类却可以定义自己的跟父类同名的属性;虽然父类的私有方法不能,但子类不能定义自己的同名方法。
6. 最终类和最终方法
最终类,其实就是一种特殊要求的类,要求该类不允许向下继承下去。
形式:
final class 类名{
//类的成员定义,同一般类。
}
最终方法,就是一个不允许下级类去覆盖的方法。
形式:
class 类名{
final function 方法名(形参列表){
//方法体
}
}
五、 设计模式
设计模式就是解决某个问题的一般性代码模式的经验总结。两种常见的设计模式,工厂模式和单例模式。
1. 工厂模式
工厂模式是这样的类,它可以根据传递给他的类,而去生产出对应的类的对象。
<?php
class A{}
class B{}
class Factory{
static functionGetObject($classname){
$obj = new$classname();
return $obj;
}
}
$o1 = Factory::GetObject("A");
$o2 = Factory::GetObject("B");
$o3 = Factory::GetObject("A");
var_dump($o1);echo "<br />";
var_dump($o2);echo "<br />";
var_dump($o3);echo "<br />";
?>
2. 单例模式
单例就是一个对象,单例模式就是这个类只能创造出来一个对象的模式。
<?php
class Single{
private function__construct(){
}
static $instance = null;
static functionGetObject(){
if(!isset(self::$instance)){
$obj = new self();
self::$instance= $obj;
returnself::$instance;
}else{
returnself::$instance;
}
}
}
$obj1 = Single::GetObject();
$obj2 = Single::GetObject();
var_dump($obj1);echo "<br />";
var_dump($obj2);echo "<br />";
?>
六、 类的扩大化技术
1. 抽象类和抽象方法
抽象类是一个不能被实例化的类。
定义形式:
abstract class 类名{}s
抽象类是为了技术管理而设计的。
抽象方法是一种只有方法头,没有方法体的定义方法的方式。也是为了技术管理而设计的。要求下级去实现这个抽象方法。
定义形式:
abstract class function 方法名(参数列表);//注意结尾必须要分号。
<?php
abstract class Boss{
protected $blood = 100;
abstract protected functionAttach();
}
class Snake extends Boss{
protected functionAttach(){
echo"<br />发射一口";
$this->blood--;
}
}
class Mao extends Boss{
protected functionAttach(){
echo"<br />发射一抓子";
$this->blood--;
}
}
?>
注意:抽象方法必须在抽象类中,抽象类中可以没有抽象方法。抽象方法是为了规定下级类中必须做什么。下级类中继承了上级类中的抽象方法,要么去实现该方法的具体内容,要么自己将这个方法规定为抽象方法。子类实现父类的抽象方法的时候,其形参也应该跟父类保持一致,其访问权限也不能降低,原因是这其实是重写,应该遵循重写的要求。
2. 重载
overloading,一般的面向对象语言中,在一个类中,有多个名字相同但形参不同的方法的现象。
形如:
class C{
function f1(){}
function f2($p2){}
function f3($p1, $p2){}
}
重载在php中的含义是,当对一个对象或类使用其未定义的属性或方法的时候,其中的一些处理机制。php中的重载与其他面向对象语言中的重载是不同的。
<?php
class A{
public $p1 = 1;
//一旦类中定义了__get方法,在出现未定义属性时,重载技术就解决了报错的问题。
function __get($prop_name){
echo "<br/>属性{$prop_name}没有定义";
}
}
$a = new A();
echo $a ->q;
echo $a ->p1;
?>
1) 属性重载,就是对一个对象的不存在的属性进行使用的时候,这个类中预先设定好的应对方法(处理机制)。
属性,本质,就是变量,只有4个操作,
取值,当对一个对象的不存在的属性进行取值时,就会自动调用方法:__GET()
赋值,当对一个对象的不存在的属性进行赋值时,就会自动调用方法:__SET()
判断(isset),当对一个对象的不存在的属性进行判断时,就会自动调用方法:__isset ()
销毁(unset),当对一个对象的不存在的属性进行销毁时,就会自动调用方法:__unset()
以上4种方法称为魔术方法。
__GET($属性名)
在对一个对象的不存在的属性进行取值时,会自动调用该方法;其实可以使用该方法来对这种意外情况进行某种特别的处理;其中,该方法可以带一个参数,表示这个要对之取值而不存在的属性名。
<?php
class A{
public $p1 = 1;
//一旦类中定义了__get方法,在出现属性重载时就解决了报错的问题。
function __get($prop_name){
echo "<br/>属性{$prop_name}没有定义";
echo "<br/>属性{$prop_name}不存在";
return "";
trigger_error("放生错误,属性不存在", E_USER_ERROR);
die();
}
}
$a = new A();
echo $a ->q;
echo $a ->p1;
?>
__SET($属性名,值)
在对一个对象的不存在的属性进行赋值时,会自动调用该方法;它有2个形参,分别代表对之不存在的属性进行赋值的时候的属性名和属性值。
这个方法结合__GET方法,可以使定义的类,具有可简便扩展属性的特性,即类的属性,可以更为灵活自由。
<?php
class A{
//定义属性,用于存储若干个不存在的属性
public $prop_list =array();
//一旦类中定义了__get方法,在出现属性重载时就解决了报错的问题。
function __set($p, $v){
//当给不存在的属性赋值时
$this->prop_list[$p] = $v;
}
function __get($p){
return $this->prop_list[$p];
}
}
$a = new A();
echo $a ->q = 1;
echo $a ->p1 = 2;
echo "<br />不存在的属性的值:";
echo "<br />q" . $a ->q;
echo "<br />p1" .$a ->p1;
?>
__ISSET($属性名)
当一个对象的不存在的属性进行isset()判断的时候,就会自动调用内部方法__ISSET();
用法:
$v1 = isset($对象 –> 不存在的属性); //此时,就会调用这个对象的所属类中的魔术方法,__ISSET()
function __isset($prop){
$v1 = isset($this->prop_list[$prop]);
if($v1 ===true){
return true;
}else{
return false;
}
}
$v1 = isset($a ->q);
$v2 = isset($a ->abc);
echo "<hr />";
var_dump($v1);
echo "<br />";
var_dump($v2);
__UNSET($属性名)
当对一个对象的不存在的属性进行unset()销毁操作的时候,就会自动调用这个方法。
function __unset($prop){
unset($this->prop_list[$prop]);
}
unset($a ->p1);
echo "<br />p1:" . $a ->p1;
4个方法的完整测试代码:
<?php
class A{
//定义属性,用于存储若干个不存在的属性
public $prop_list =array();
//一旦类中定义了__get方法,在出现属性重载时就解决了报错的问题。
function __set($p, $v){
//当给不存在的属性赋值时
$this->prop_list[$p] = $v;
}
function __get($p){
if(isset($a->p)){
return $this->prop_list[$p];
}else{
echo "这个属性不存在";
}
}
function __isset($prop){
$v1 = isset($this->prop_list[$prop]);
if($v1 ===true){
return true;
}else{
return false;
}
}
function __unset($prop){
unset($this->prop_list[$prop]);
}
}
$a = new A();
echo $a ->q = 1;
echo $a ->p1 = 2;
echo "<br />不存在的属性的值:";
echo "<br />q" . $a ->q;
echo "<br />p1" .$a ->p1;
$v1 = isset($a ->q);
$v2 = isset($a ->abc);
echo "<hr />";
var_dump($v1);
echo "<br />";
var_dump($v2);
echo "<hr />";
unset($a ->p1);
echo "<br />p1:" . $a ->p1;
?>
2) 方法重载
当对一个对象的不存在的方法进行调用的时候,会自动调用类中的__call()这个魔术方法;
当对一个类的不存在的静态方法进行调用的时候,会自动调用类中的__callstatic()这个魔术方法。
<?php
class A{
function__call($methodname, $argument){
echo"{$methodname}这个方法不存在";
}
}
$a1 = new A();
$a1 ->b();
?>
案例,设计一个类,这个类的实例,可以实现如下需求,传入1个参数,就返回其本身,传入2个参数,就求其平方和,传入3个参数,就求其立方和,否则,报错。
<?php
class A{
function __call($methodname,$argument){
echo"{$methodname}这个方法不存在";
if(count($argument)==1){
echo"<br />" . $argument[0];
return$argument[0];
}elseif(count($argument) ==2){
$val =$argument[0]*$argument[0]+$argument[1]*$argument[1];
echo"<br />" . $val;
return $val;
}elseif(count($argument) ==3){
$val =$argument[0]*$argument[0]+$argument[1]*$argument[1]+$argument[2]*$argument[2];
echo"<br />" . $val;
return $val;
}else{
echo "请输入1个或2个或3个数字";
}
}
}
$a1 = new A();
$b1 = $a1 ->b('2','2');
echo "<br />b1=" . $b1;
?>
3. 接口
Interface,就是规定,里面只能放抽象方法和常量的一种类似类的结构体。
定义形式
interface 接口名{
常量1;
常量2;
……
抽象方法1;
抽象方法2;
……
}
接口中,只有常量(又叫接口常量)和抽象方法两种成员,接口常量的使用形式是:接口名称::常量名。接口的抽象方法,不需要使用abstract修饰,也不需要使用访问控制修饰符,因为天然就是public。
<?php
interface I1{
const PI = 3.14;
const C = 22;
function Show1();
function func2($p1, $p2);
}
?>
面向对象编程思想是对现实世界的描述,现实世界是多继承的,出于降低类之间关系的复杂度的考虑,将语言设计为单继承的,但这样,就无法表现出现实世界的多继承特性,接口就是对类的单继承的一个补充。因为,接口可以实现多继承,此时不称为继承,而是称为实现。即,接口1 -- >>类1;称为类1实现了接口1,本质上就是类1中有了接口1中的特征信息。
<?php
interface Player{
function play();
function stop();
function next();
function prew();
}
interface Usbset{
const USBWIDTH = 12;
const USBHEIGHT = 5;
function dataIn();
function dataOut();
}
class Mp3 implements Player,Usbset{
function play(){} //实现接口的方法
function stop(){}
function next(){}
function prew(){}
function dataIn(){}
function dataOut(){}
}
?>
接口和接口之间,也可以继承。形如,
interface 接口1 extends 接口2{
//接口的成员定义
}
七、 类或对象的相关技术
1. 类的自动加载
当某行代码需要一个类的时候,php的内部机制可以做到自动加载该类文件,以满足该行需要一个类的这种需求。
类被需要的情况,new 一个对象的时候,使用一个类的静态方法的时候,定义一个类并以另一个类作为父类的时候。
<?php
//require_once './mysqlUtil.class.php";
function __autoload($name){
require_once './' . $name .".class.php";
}
$config = array();
$d = mysqlUtil::GetInstance($config);
var_dump($d);
?>
类自动加载的条件和要求:当需要一个类的时候,就会自动调用某个函数(默认是__autoload()),并传入需要的类的名字;一个类应该保存到一个独立的类文件中,即其中只有该类的定义,没有别的代码;习惯上,类文件的命名要有一定的规则,通常是,类名.class.php;通常,将各种类,存储在一些特定的目录中,以方便确定其位置;在该自动加载的函数中,充分使用传过来的类名,
自定义自动加载函数,除了使用__autoload()方法外,可以使用更多函数(自定义的),来实现更灵活的自动加载。
基本模式为:
spl_autoload_register(“函数1”); //声明函数1为自动加载函数。
spl_autoload_register(“函数2”); //声明函数2为自动加载函数。
spl_autoload_register(“函数3”); //声明函数3为自动加载函数。
。。。
然后,定义这些函数,跟定义__autoload()函数一样。
即
function 函数1($class_name){
//…
}
function 函数2($class_name){
//…
}
……
例子:
<?php
spl_autoload_register("autoload1");
spl_autoload_register("autoload2");
function autoload1($class_name){
//自定义自动加载函数,用于加载class目录的的类文件
$file = './class/' .$class_name . ".class.php";
if(file_exists($file)){
include_once $file;
}
}
function autoload2($class_name){//自定义自动加载函数,用于加载lib目录的的类文件
//自定义自动加载函数,用于加载class目录的的类文件
$file = './lib/' .$class_name . ".class.php";
if(file_exists($file)){
include_once $file;
}
}
$a = new A();
var_dump($a);
$b = new B();
var_dump($b);
?>
2. 对象的复制
对象的复制,相当于将一个对象制作双份的语法,复制后是不同的对象,对象地址不同,但是内容一样。类似普通数据的值传递。
语法:
$o1 = clone $o2; //复制一个完全一样的。
比如:
<?php
class A{
public $p = 1;
}
$o1 = new A();
$o1 ->p = 11;
$o2 = $o1;
var_dump($o1);
echo "<br />";
var_dump($o2);
$o3 = clone $o1;
echo "<br />";
var_dump($o3);
$o1 ->p = 23;
echo "<hr />";
var_dump($o1);
echo "<br />";
var_dump($o2);
echo "<br />";
var_dump($o3);
?>
3. 对象的遍历
对象的遍历,和数组的遍历一样。实际上,只能是遍历出对象的实例属性数据,静态的属于类。
foreach($对象名 as $key =>$value){
//然后可以处理$key和$value
//注意:$key表示的是对象的属性,$value表示的是对象的值;可以遍历的属性,只能是在该范围内的可访问属性。
}
比如:
<?php
class A{
public $p1 = 1;
protected $p2 = 2;
private $p3 = 3;
static $p4 = 4;
}
$o = new A();
foreach($o as $key => $value){
echo "<br/>$key---$value";
}
?>
改进后的遍历方法
<?php
class A{
public $p1 = 1;
protected $p2 = 2;
private $p3 = 3;
static $p4 = 4;
function showAllProp(){
foreach($this as$key => $value){
echo"<br />$key---$value";
}
}
}
$o = new A();
$o ->showAllProp();
?>
4. php内置标准类
php中有很多现成的类,其中,有一个被称为内置标准类。这个类内部什么都没有。类似:
class stdclass{}
<?php
$o = new stdclass();
var_dump($o);
?>
作用是存储一些临时的简单的数据,比如:
<?php
$o = new stdclass();
var_dump($o);
$o ->p = 1;
$o ->port = 3306;
echo "<br />{$o ->p}";
?>
也可以用于类型转换用于存储数据。
其他数据类型转换为对象类型,得到的结果是内置标注类sdtclass的一个对象。
形如:$o = (object)其他数据类型数据;
对象转换为对象,没有变化;数组转换为对象,数组的键名作为属性名,值为对应值;null转换为对象,空对象;其他标量数据转化为对象,属性名为固定的scalar,值为该变量的值。
<?php
$config = array(
'host' =>"localhost",
'port' => 3306,
'username' =>"root",
);
$o = (object) $config;
echo "<pre>";
var_dump($o);
echo "</pre>";
echo "<br />{$o ->host}";
$b = (object)null;
echo "<br />";
var_dump($b);
$v1 = 1;
$v2 = 2;
$v3 = "abc";
$v4 = true;
$ov1 = (object)$v1;
$ov2 = (object)$v2;
$ov3 = (object)$v3;
$ov4 = (object)$v4;
echo "<br />";var_dump($ov1);
echo "<br />";var_dump($ov2);
echo "<br />";var_dump($ov3);
echo "<br />";var_dump($ov4);
?>
5. 类型约束
就是要求某个变量只能使用(接收、存储)某种指定的数据类型;php属于弱语言类型,通常不支持类型约束;而在强语言中,类型约束具有它的基本特征,比如,java中,定义一个变量,使用int a = 1;,要对某个定义的变量声明类型。
php中只支持局部的部分类型约束,php中只支持在函数或方法的形参上,设定类型的约束目标,形如:
function 方法名([要求使用的类型] $p1, [要求使用的类型] $p2, ……){
//……
}
注意:一个形参可以使用类型约束,也可以不使用。如果使用了类型约束,则对应的该实参数据,就必须是要求的那种类型。使用类型约束的情况不多,主要有,数组,array;对象,使用类的名称,表示传递过来的实参,必须是该类的实例;接口,使用接口的名称,表示传递过来的实参必须是实现了该接口的类的实例。
比如:
<?php
interface Usb{}
class A{}
class B implements Usb{}
function f1($p1, array $p2, A $p3, Usb $p4){
echo "<br />没有约束的p1:" . $p1;
echo "<br />要求是数组的p2:" . print_r($p2);
echo "<br />要求是类A的对象p3:" . var_dump($p3);
echo "<br />要求是接口Usb的实现类的对象p4:" . var_dump($p4);
}
$a = new A();
$b = new B();
$c = array();
$d = 1;
f1($d, $c, $a, $b);
?>
6. 单例类的禁止复制(克隆)
对于一个类的对象,如果使用clone,就会克隆出一个和当前对象完全一样的新对象处理,并且此时还会自动调用该类中的魔术方法:__clone(),只要其中有该方法,如果使用类的复制功能,复制单例类,会打破单例特性,要向实现单例类,就应该对这个单例类的对象禁止克隆,方法是私有化这个魔术方法__clone();
private function__clone(){}
7. 序列化与反序列化技术
序列化,就是将一个变量所代表的内存数据,转换为字符串形式并持久化保存在硬盘上的一种做法。
反序列化,就是将序列化之后保存在硬盘上的字符串数据,恢复为其原来的内存形式的变量数据的一种做法。
序列化例子:
$v1 = 12;
$s1 =serialize($v1); //将任何类型的变量数据,转换为字符串。
file_put_contents(“要保存的目标文本文件”, $s1); //将该字符串,保存在一个文件里
反序列化例子:
$s1 = file_get_contents(“保存序列化数据的目标文本文件”); //从一个文件里读出其中的所有字符
$v1 =unserialize($s1); //将该字符串数据反序列化转换为变量数据
比如:
<?php
$v1 = 1;
$v2 = 'abc';
$v3 =false;
$v4 = array(1,3,4);
$s1 = serialize($v1);
$s2 = serialize($v2);
$s3 = serialize($v3);
$s4 = serialize($v4);
file_put_contents('./file1.txt', $s1);
file_put_contents('./file2.txt', $s2);
file_put_contents('./file3.txt', $s3);
file_put_contents('./file4.txt', $s4);
?>
<?php
$s1 = file_get_contents('./file1.txt');
$s2 = file_get_contents('./file2.txt');
$s3 = file_get_contents('./file3.txt');
$s4 = file_get_contents('./file4.txt');
$v1 = unserialize($s1);
$v2 = unserialize($s2);
$v3 = unserialize($s3);
$v4 = unserialize($s4);
echo "<br />";var_dump($v1);
echo "<br />";var_dump($v2);
echo "<br />";var_dump($v3);
echo "<br />";var_dump($v4);
?>
对象的序列化,对一个对象进行序列化,只能将其属性数据保存起来,而方法被忽略,序列化时,会自动调用该对象所属类的这个魔术方法,__sleep()。
对象的反序列化,对一个对象进行反序列化,其实是恢复其原来保存起来的属性数据,而且,此时必然需要依赖该对象原本的所属类,反序列化时,会自动调用该对象所属类的这个魔术方法,__wakeup()。
对象的序列化的例子:
<?php
include_once './ObjSer.class.php';
$o1 = new ObjSer();
var_dump($o1);
$s1 = serialize($o1);
file_put_contents('./obj1.txt', $s1);
?>
对象的反序列化的例子:
<?php
include_once './ObjSer.class.php';
$s1 = file_get_contents('./obj1.txt');
$o1 = unserialize($s1);
var_dump($o1);
?>
对象的可选属性的序列化与反序列化例子:
<?php
class ObjSer{
public $p1 = 1;
public $p2 = 2;
public $p3 = 3;
static $p4 = 4;
function f1(){
echo "<br/>f1被调用";
}
function __sleep(){
echo "<br/>开始序列化操作";
//指定p1和p2两个属性进行序列化
return array('p1','p2');
}
function __wakeup(){
echo "<br/>开始反序列化操作";
}
}
?>
8. php中类的其他魔术方法
1) __tostring()
将一个对象当作一个字符串使用的时候,会自动调用该方法,并且在该方法中,可以返回一定的字符串,以表明该对象转换为字符串之后的结果。
如果没有定义该方法,则对象无法当做字符串来使用。
<?php
class A{
public $name;
public $age;
public $edu;
function __construct($name,$age, $edu){
$this ->name =$name;
$this ->age =$age;
$this ->edu =$edu;
}
function __tostring(){
$str = $this->name . '--' . $this ->age . '--' . $this ->edu;
return $str;
}
}
$o1 = new A("hh", 12, "小学");
echo $o1;
?>
2) __invoke()
将对象当作函数来使用的时候,会自动调用该方法。比如:
class B{
function __invoke(){
echo "<br/>这不是一个方法,请检查";
}
}
$b = new B();
$b()
9. 类中中其他常量、函数和运算符
1) 与类有关的魔术常量
一般的魔术常量例子,如__FILE__ __DIR__ __LINE__。
与类有关的魔术常量,__CLASS__,表示当前其所在类的类名;__METHOD__,表示其当前所在的方法名。
例子:
class C{
function f1(){
echo "<br/>__DIR__:" . __DIR__;
echo "<br/>__FILE__:" . __FILE__;
echo "<br/>__LINE__:" . __LINE__;
echo "<br/>__CLASS__:" . __CLASS__;
echo "<br/>__METHOD__:" . __METHOD__;
echo "<br/>__LINE__:" . __LINE__;
}
}
$c = new C();
$c ->f1();
2) 与类有关的系统函数
class_exists(“类名”);判断一个类是否存在
interface_exists(“接口名”);判断一个接口是否存在
get_class($obj);获得一个对象的所属类
get_parent_class($obj);获得某个对象的所属类的父类
get_class_method();获得一个类的所有方法,结果为数组,里面存储方法的名称。
get_class_vars();获得一个类的所有属性,结果为数组,里面存储的是属性的名称。
get_declared_classes();获得整个系统所有定义的类名,结果为数组,里面存储的是类的名称。
比如:
$cc = get_declared_classes();
echo "<pre>";
var_dump($cc);
echo "</pre>";
3) 与对象有关的系统函数
is_object();//判断某个变量是否是一个对象
get_object_vars($obj);//获得一个对象的所有属性,结果为数组,里面存储属性的名称
$g = get_object_vars($o1);
var_dump($g);
4) 与类有关的运算符
new
instanceof//判断一个变量是否是某个类的实例。
if($o1 instanceof C){
echo "<br />是C的实例";
}else{
echo "<br />不是C的实例";
}
10. static的总结
static也可以向self一样,代表当前类,用于访问一个类的静态属性或静态方法,但是static,在应用中,更灵活,因此更常见。static代表调用当前方法的类,而不是其代码所在的类;self只代表这个单词本身所在位置的所在类。
class S{
static $p1 = 1;
static function show1(){
echo "<br/>self::p1=" . self::$p1;
echo "<br/>static::p1=" . static::$p1;
}
}
class S2 extends S{
static $p1 = 111;
}
S::show1();
echo "<br />";
S2::show1();
static用法见表:
含义 | 位置示例 |
代表当前函数或方法中的静态变量 | function f1(){ static $v1 = 1; } |
代表类中的静态成员 | class a{ static $p1 = 1; static function f1(){…} } |
代表调用当前方法的当前类 | class a{ function f1(){ echo static::属性或方法 } } |
八、 面向对象编程的3个特征
封装:
是一个大的指向思想,目的是为了将一个类设计的更加健壮。
基本做法是,尽可能将一个类的成员私有化,只放开那些不可少的对外开放的属性或方法。
继承:
是面向对象的基本思想和基本做法,继承是代码的一种重要机制。
多态:
就是多种形态,为了描述现实世界的丰富多彩的表现形式。在实际代码中,多态常有的两种表现形式是:不同的对象,使用相同的方法,会表现为不同的结果;同一个对象,使用相同的方法,也可能会表现为不同的结果,这其实是方法的重载现象。