天天看点

如何编写一个正确的php类,并正确的使用它

问:如何编写一个正确的php类?

答:这个问题,我们首先要明白php的类是什么?很多人都会说,这不就是对象的抽象出来的东西么,用关键字class来定义的。是的,没错,这样说一定问题都没有。但是这里我们要把类的范围扩展一下。这里的类我们包含普通的类(class)以及抽象类(abstract class)和接口(interface)以及特性(trait)。我们把他们都归类到“类中”,关于trait的介绍,请看我其它博文,这里我们侧重点不同。

首先来看一个正确的普通类中应该包含什么东西。首先要使用class关键字定义,在花括号内包含成员属性和成员方法。另外注意关于类的编写风格最好是能严格遵循psr-2标准。也就是说一个完整的类中就包含了两类东西“属性”(变量+常量)+“方法”。

那么抽象类呢有何不同呢?哈哈,因为它抽象呗。对于抽象对象的理解呢,注意点应该放在抽象方法上。怎么理解这句话呢,如果类中有一个方法是抽象方法,那这个类必须用abstract来定义成抽象类。抽象类是一个半成品类,他只能被继承,让子类去实现那些抽象方法,才能最终被实例化,否则永远不能实例化抽象类。另外,对于那些不是抽象的方法和属性,访问权限是不能设置为private的,因为抽象类不能实例化,子类继承后,无法调用父类是抽象类的私有方法和属性,而父类自己调用的话还得使用self,但是它又不能实例化,所以这种方法将会是个死方法,永远无法调用。总结抽象类与抽象方法的关系就是,若类中包含抽象方法则该类必须设置为abstract class。若不包含抽象方法本质是一个正常的类,也可以用abstract class来定义该类,那样他就不能实例化了只能被继承了。

接口又是有啥不同呢?哈哈,它是一种特殊的抽象类,它里面全是抽象方法,同时允许有常量属性,不允许有变量属性。接口的虽然他是一个特殊的抽象类,但不能使用abstract class来定义了,因为他特殊啊,所以要用interface关键词来定义接口,接口要求里面的方法全部是抽象方法,所以不必显示的定义方法为abstract,但是要求访问属性必须为public或者不写(默认是public),这是因为如或是其他类型的比如protected或者private等权限,实现类在实现了这些方法后,最多只能在在本类中使用,私有的甚至都无法使用,所以必须使用public,这样才可以在类外边调用该方法。那么问题又来了,我们如何实现接口的方法呢?再定义一个类,然后使用implement实现接口。需要注意的是,可以用抽象类实现接口部分方法,用普通类实现接口全部方法。一个接口的方法只有被全部实现后,才能实例化。

当然接口也可以继承接口,使用extends。同时一个普通类可以继承(extends)一个父类(普通类或者抽象类)然后实现多个接口(接口用逗号分隔)。是不是很神奇,哈哈。我们都知道php的类只能有一个父类,但可以实现多个接口。此外我们在另一篇关于trait的介绍中,也实现了“多继承”的伪实现。关于特质的更多请查看我的其它博文。

下面我们来说一下,面向对象的一个关键问题---三大特性。封装性、继承性、多态性。

封装性:就是要求对象以外的部分不能随意存取对象的内部数据(成员属性成员方法),这一特性往往是通过设置访问权限关键字来实现。Private只能在本类中调用,protected可在子类中进行调用,public可在外部调用,一层比一层权限大。

继承性:就是在现有类的基础上派生出具有新功能的类,php只支持单继承!使用extends关键词来实现继承。毕竟是两个类之间的关系,如何保证上面说的封装性?访问控制关键字已经做了处理,所以不用担心。另外继承就会遇到同名方法或属性的问题(要知道,php中不存在方法重构,因为他不允许函数或者同一类中方法同名),那父类和子类本质上也是两个类,但它允许同名,这样出现同名方法后,由子类生成的对象调用某个同名的方法时,会调用子类的方法,而忽略了父类的方法,属性也一样。怎么通过子类调用父类权限允许的方法呢?那就是在子类中的方法使用parent::方法名(),即可实现子类中对父类方法的调用。但是也要清楚一个规定:重写的方法参数个数最好要一样,不然5.3之后会有错误提示。另外有一条铁律要注意:在子类中重写的方法的访问权限一定不能低于父类被覆盖的方法的访问权限!(这是因为若把子类对象以父类类型作为参数传入某个方法或函数时,若使用到重写的方法时,权限缩小会导致方法可能不可用,再明白点说,父类方法可调用,子类的不可调用!)

多态性:直白讲就是龙生九子,各有不同!不同类继承同一个父类,并重写同一个父类方法。(只要每个子类都有这个方法就行,这往往用接口或抽象类来做要求)这样每个子类实例化后调用该方法会有不同的表现形式。插件对此运用的比较完美,主程序会调用每个插件的run方法,只要插件中实现了run方法就能被主程序调用。(你有我有全都有啊!这也可以看出来多态性是针对继承后子类们的表现来表达的,其实完全可以不用继承直接定义run方法就行,但这就不是多态表达的意义了)事实上php这点的多态性和C++语言的多态性出现了分歧,C++的多态性表现在在同一个类中函数重载上(也是基于继承的基础),php则是在不同的子类中。另外有个提醒,在php5.3之后重写的方法最好能和父类方法的参数个数是一样的,不然会爆出strict standards,不影响,只是代码建议,可通过设置错误级别来忽略。

以上我们说了很多关于集成之后重写父类方法的一些注意点,这里再补充和归纳剩余的注意点。子类方法权限不能限缩,重写的子类方法最好与父类参数个数相同(5.3分界线),父类方法用final标识的,子类可以继承但不能重写。

是的,正如上面提到的final关键字一样,在类中,我们经常会用到一些关键字,下面我们来说说这些关键字。Final+static+const等。

Final可以用来标识类和类方法,但不能标识成员属性。标识类该类不能被继承,标识方法该方法不能被重写。

Static可以标识成员属性和成员方法,还可以标识普通的变量(一般针对局部变量,是为了让变量不消失,全局变量本来就不消失,用在他身上没意义)。被他标记过,在脚本运行期间不会释放掉,只初始化一次,保留每次计算的值。他有专门的内存存放区域,多个对象共享static成员属性。类的静态属性非常像函数的全局变量。

Const用来定义在类中的常量,类外定义常量都是使用define函数来定义。常量一旦定义就不能改动,所以一定要附初始值。访问它只有两种方法,使用类名或者self,这一点和静态成员属性一样,如self::NAME。关于访问类中所有成员,待会有专门的片段对比讲解。

Instanceof用于确定一个 PHP 变量是否属于某一类 class 的实例,也可用来确定一个变量是不是继承自某一父类的子类的实例,用于确定一个变量是不是实现了某个接口的对象的实例,instanceof 通常直接与类名一起使用,但也可以使用对象或字符串变量,如果被检测的变量不是对象,instanceof 并不发出任何错误信息而是返回 FALSE。不允许用来检测常量, instanceof 的使用还有一些陷阱必须了解。在 PHP 5.1.0 之前,如果要检查的类名称不存在,instanceof 会调用__autoload()。另外,如果该类没有被装载则会产生一个致命错误。可以通过使用动态类引用或用一个包含类名的字符串变量来避开这种问题。例如 $d = 'NotMyClass';

var_dump($a instanceof $d);//这样就避免加载类了。

以上便是我们在编写一个正确“类”指导方针了。

问:那我们如何去调用我们编写好的类中的成员呢?

答:是的,我们写好类,目的是为了用,那怎么样在类外部程序中对类中不同的成员进行调用呢。下面我们来分类介绍。

对象对所有成员成员方法和普通成员属性有一种通用(不用区分静态还是非静态)访问方式就是通过->的方式。但是对静态成员属性不能使用->这种方式

即:$obj = new class名(参数列表,若无参数,连括号也可以省略);

$obj->(普通)成员属性;$obj->(静态或非静态)成员方法名();

类中的静态成员属性和静态成员方法使用类名来直接访问的;

即:类名::静态成员属性名;

类名::静态成员方法名();

注意上面这种::方式不光可以在类外部可以使用,还可以在成员方法中使用。

另外,在成员方法中还可以使用self这种方式。Self代表本类,$this代表本对象

Self::静态成员属性名;

Self::静态成员方法名();

顺便说一下对静态成员调用时,调用方式使用习惯。再类中一般使用self,在类外使用类名,基本上不适用对象。就这样!

关于静态调用有这么几个注意点:

  1. 静态方法不能调用非静态属性。
  2. 若使用self调用非静态方法,系统会自动将该方法转为静态方法,注意此时的$this的使用。
  3. 静态方法中不能使用$this(用来调用非静态方法或属性)

问:为什么会有这些注意点呢?

答:这其实和对象在内存中的存储方式有关。下面我们来看一下内存中是怎么保存对象的。

程序加载到内存中才能运行。内存根据自己的特点分成四种类型

栈内存+堆内存+数据段+代码段

栈主要存放整形、浮点型、布尔型、对象的引用等临时变量,他们存储的内容有一个特点就是长度不变,占用空间小,要随时被直接访问。因为栈有cpu访问快,空间小,后进先出等特点,所以适用于临时数据交换存储,保护和恢复现场。

堆内存用于长度可变,占用空间大的变量,例如字符串,数组和对象本身。

数据段主要存储程序静态分配的变量如(已初始化的)全局变量,常量,静态变量等。

代码段存放的就是程序(可执行文件)指令,如程序中的函数存放在这,因为要保证程序运行时避免非法修改,因此该部分只读。

说到这你是不是就明白为什么要区分静态和非静态的场景了,根本就是,静态中若使用非静态的元素的话,他可能不存在,你说这不耽误事嘛。

对了关于对象调用类中常量,参考const的介绍。

关于调用方法的知识是不是感觉到此结束了?不,还有呢,那就是回调!!这部分我打算新开一篇博文来介绍。其实严格说来我们之前说过的反射机制也是一种调用方法,所以认真领悟不同场景下如何调用方法,真的很重要!