标签归档:面向对象

PHP面向对象的历史

PHP面向对象的历史

PHP最开始的perl脚本,到C语言版的PHP/FI,再到PHP/FI 2.0、PHP3.0,直到PHP4,引入Zend Engine使PHP更加的强大,并在PHP5引入新的Zend Engine2,重写PHP的面向对象模型,使PHP不仅可以快速开发,同时也可以实现更加复杂的架构,甚至满足企业应用。

PHP最开始并没有面向对象,直到PHP4才有一些面向对象的影子,到PHP5才真正实现面向对象模型。大概来说,PHP面向对象历史包括两个阶段:

PHP4-Zend Engine阶段

此时并没有真正的面向对象,因为PHP根本没有实现面向对象的三大特性,所有的成员方法和成员函数都是公有的,成员变量通过var声明。

此时的构造函数和类名一样,序列和反序列化时能调用魔术函数_sleep 和 __wakeup。嗯,这是是叫魔术函数而不是魔术方法,因为它本来就是独立出来的函数,当执行序列化时,PHP会判断当前变量是什么类型,如果是IS_OBJECT,则会自动调用__sleep函数。

在4.0.2以后可以使用parent::调用父类的方法。这里的parent仅仅是函数调用时的一个特殊处理。

在PHP的内核实现中类和函数共用一个opcode(ZEND_DECLARE_FUNCTION_OR_CLASS),通过extended_value字段区分,类和函数的存储已经区分开。

总的来说,PHP4的面向对象有点脚手架的味道,各种定制后有了一些面向对象的形。

PHP5-Zend Engine2阶段

5.0.0引入Zend Engine2,至此PHP才真正引入了面向对象的机制。
Zend Engine2重写了PHP的面向对象模型,其中包括对构建器和析构器的定义,增加的私有成员变量、静态成员变量、接口、重载等面向对象特性以及新增加了魔术方法实现。除了面向对象特性外,Zend Engine2引入了异常处理控制流。具体见: http://www.zend.com/engine2/ZendEngine-2.0.pdf

5.1.0 新增:__isset 和 __unset 方法。

5.3.0 新增: __invoke 方法、 后期静态绑定、 heredoc 和 nowdoc 支持类的常量和属性的定义、__callStatic 方法等

5.4.0 增加Traits,Trait 和类相似,但它的目的是用细粒度和一致的方式来组合功能。Trait 不能实例化。它为传统继承增加了水平特性的组合;也就是说,应用类的成员不需要继承。

总的来说,PHP5已经实现了面向对象模型,可以基于PHP5实现企业级应用。但是一些新的功能和特性,在实际的开发过程中使用得并不多,如Traits、命名空间等。很多时候,业务决定技术,需求决定实现。

然,此篇仅为整理之作,只为理自己对于PHP面向对象的思路。
久不沾笔,些许生疏。

参考资料

  • http://php.net/manual/zh/history.php.php
  • http://www.php.net/ChangeLog-4.php
  • http://www.php.net/ChangeLog-5.php

思考PHP之五:访问控制

面向对象三大特性:封装性、继承性和多态性。 封装隐藏了对象内部的细节和实现, 使对象能够集中而完整的描述并对应一个具体的事物。 它使对象只提供对外的访问接口,这样可以在不改变接口的前提下改变实现细节,而且能使对象自我完备。 除此之外,封装还可以增强安全性和简化编程。 继承的目的是为了实现代码复用,它是一种一般化与特殊化的关系,其中子类是父类的细化。 在实现继承时最需要考虑的问题是子类和父类是不是”IS-A”的关系。

PHP(其它面向对象的语言也类似)对于封装和继承的一些特性是通过访问控制实现。访问控制的作用是控制成员变量,成员方法和基类。 曾经一直以为访问控制的作用仅仅是控制一个类的成员方法和成员变量,这把自己的思维局限于一类一对象了, 这两个方面的控制是PHP对面向对象中封装特性的支持。 把思维拉升到面向对象的体系之上,访问控制也控制了基类(或父类)的行为,或者说控制了继承特性的某些方面。

PHP中关于访问控制的关键字和Java等其它面向对象语言一样,如下:

  • public 所定义的类成员可以在任何地方被访问
  • protected 所定义的类成员则可以被其所在类、其所在类的子类和父类访问
  • private 定义的类成员则只能被其所在类访问。

以上的类成员包括成员变量和成员函数。不管是成员变量还是成员方法,PHP默认都是public。 在Java中访问控制默认为包可见,在C++中访问控制默认为私有(private),而PHP则是公有的(public),这比Java还要open。 笔者认为这是PHP的一个历史遗留问题。如果可以重新设计PHP,可能是另一个结果,并且这也是语言的对于访问的态度问题。

前面介绍的各个访问控制是针对封装性,对于继承性,如下:

  • public/protected 可以被继承
  • private 没有被继承

实际上,在PHP中,私有方法也会被继承下来,只是其上下文没有改变(还是父类),从而在调用的时候出错。

一般来说,private定义的成员只能被内部调用,仅供当前类使用,这在PHP的源码中检查访问权限控制时, 以private的成员会检查是否属于当前类体现。public定义的成员则属于类或对象的外部接口, 声明的public成员最好是定义好后就不要再变更,这会影响到调用了类的这些方法的相关客户。 好的public和private的设计对于对象本身的自我完备的实际有重大的意义。

但是public关键字有一些二义性。对于封装性,它是公有的,任何地方都可以访问的成员;对于继承性, 它允许子类继承此成员。同时兼顾这两个特性,当我们把它作为一个接口提供给外部使用时就会有一些歧义: 子类可以覆盖该成员方法,同时也可以调用访方法,如果子类覆盖了该成员方法并调用了该方法, 则它的实现就和你当初作为接口提供给外部时的含义有一些不同了。和public一样,protected也有类似的问题。 可以思考一下:各语言这样实现的目的是什么?是否有更好的方案?

思考PHP语言二:面向对象

思考PHP语言二:面向对象
【概述】
PHP是一门支持面向过程,也支持面向对象的动态语言。从PHP5开始,PHP对于面向对象的支持好了很多。
面向对象程序设计是抽象数据类型的抽象原理的一种应用。当我们使用类时,我们应该了解这是一种抽象数据类型,对象是属于所对应类的类型的。
面向对象的三个特征:封装,继承,多态。这三个特征也可以用:抽象数据类型,继承和方法调用与方法间的动态绑定来说明。抽象是我们与程序复杂性抗争的一种方式,通过这种方式,我们可以关注并且仅关注主要的属性而忽略次要的属性。从语法的角度来说,抽象数据类型是一种封装,它不仅仅包括对于数据的封装,也包括其提供方法的封装。通过访问控制,从而实现数据或方法的隐藏和暴露,这就是我们经常所说的类,它的实例我们称之为对象。

【PHP的抽象数据类型】
PHP以类的形式实现封装,类中所定义的数据称为成员变量;而类中所定义的函数称为成员函数或成员方法。成员函数和成员变量又分别有类属性和实例属性。
类属性的成员函数和成员变量
PHP中的类属性成员都以静态的方式存在,为所有的实例共享(包括成员函数和成员变量),可以通过类名直接调用。但是其仅在一个PHP的生命周期内有效,这与java等有较大的差别。
实例属性的成员函数和成员变量
对于实例属性的成员函数和成员变量,一个类的所有实例共享一套成员函数,而每个实例都有属于自己的一套成员变量,这也是我们在做面向对象的程序结构设计时将数据尽量向下移,将共用的方法尽量向上移的原因。
信息隐藏
PHP中的信息隐藏通过访问控制实现,对于需要隐藏的数据将其以私有成员定义,在其声明前添加private关键字,对于需要公开的数据将其以公有成员定义,在其定义前添加public关键字,也可以不加任何关键字,因为PHP默认情况下是public。PHP的私有和公有访问权限与java相同,

命名封装
在构建大型系统时,为解决不同团队对于函数或类等的命名冲突,我们需要引入命名封装,命名封装是一些逻辑上的封装。在PHP中使用命名空间实现,只是这在PHP5.3版本后才有相关支持。
在PHP中,类,函数和常量这三种类型受命名空间的影响,命名空间通过关键字namespace 来声明,对于层次化的命名空间以反斜杠隔开,如:namespace com\toll\level;

【PHP的继承】
继承是软件复用的一种形式,它不仅可以让程序人员以一个已有类为基础,设计一个修改了的后代类型,还可以定义相关的程序结构和关系。这种结构和关系在一定程度上反映了这些实体的上下代关系。从这里就产生了父类,子类等概念,并且在访问控制中也产生了protected,使用protected时,它的最大访问范围为其派生类。在子类中我们可以定义新的成员变量和新的成员函数,并且可以重载父类的成员函数,但是无法重载父类的构造函数,如果在实例化时需要调用父类的构造函数,我们需要手动调用父类的构造函数,PHP本身不会自动调用。
另外,关于PHP的构造函数的一个历史遗留问题:以类名为构造函数和以__construct为构造函数,在之前写过一篇文章介绍二者在调用时的选择方式。PHP源码阅读笔记二十七:PHP对构造方法的识别
PHP没有多继承,只支持单继承,但是他拥有接口,它提供了一种多继承的方式。这点和java一样。
接口的定义与类的定义类似,只是关键字变成了interface,接口可以包含常量和方法声明。它的作用仅仅是定义类的说明。类可以实现多个接口,但是类也必须实现接口中的所有方法。接口可以模拟实现多继承,也提供了另一种形式的多态,但是这对于PHP来说没有多大的意义,因为PHP的变量本身就是多态的。

继承是作为增加复用可能性的一种手段,但是它在继承层次中产生的类之间的相互依赖性,产生了一些强耦合。而这与之前的封装的优点刚好相反,封装就是为了保持类之间的相互独立性。所以在作设计时,我们如果使用继承就需要考虑是否真的需要,是否真的是is-a的关系。更多的时候我们会选择使用委托来实现。
【PHP的动态绑定】
PHP本身就是动态绑定的。