TIPI儿童节版发布

少年智则国智,少年富则国富,少年强则国强,少年独立则国独立,少年自由则国自由,少年进步则国进步……

各位自认为是儿童的,不是儿童的;扮萌的或已成为大叔的同学,儿童节快乐! TIPI团队选择在这个应该纯粹一点的节日里,发布努力了两个月的成果。 自最近一次的版本发布(2011-04-01)以来,关注的同学可能会注意到网站上 没有新的变化,其实这段时间我们并没有减缓TIPI项目的进度,在第一次项目发布之后我们收到了很多的反馈, 经过团队的讨论,我们决定暂缓新章节的编写,把精力集中在现有章节的改良上。 总体来讲有如下变化:

  • 重构现有章节。将现有一些章节的内容进行了丰富。当然这并不是终点,我们会持续对内容进行优化。
  • 完成了第五章的编写。上次发布时发布了第五章类的第一二小节,这次我们完成了所有的内容。

上次发布时我们提供了PDF版本的下载, 这次的发布没有带来新格式的下载,不过不要着急, CHM版本的下载将会在近期提供。

TIPI入口>>>

理解PHP中的stdClass类

在百度百科中,对于stdClass的定义如下:

stdClass在PHP5才开始被流行。而stdClass也是zend的一个保留类。stdClass是PHP的一个基类,
所有的类几乎都继承这个类,所以任何时候都可以被new,可以让这个变量成为一个object。同时,
这个基类又有一个特殊的地方,就是没有方法。凡是用new stdClass()的变量,
都不可能会出现$a->test()这种方式的使用。PHP5的对象的独特性,对象在任何地方被调用,
都是引用地址型的,所以相对消耗的资源会少一点。在其它页面为它赋值时是直接修改,而不是引用一个拷贝。

以上的定义大多数都是正确的,但是一个致命性的诊断错误: stdClass是PHP的一个基类,所有的类几乎都继承这个类。 看一个简单的例子:

class EmptyClass {

}

$object = new EmptyClass();
if ($object instanceof stdClass) {
    echo 'yes';
}else{
    echo 'no';
}

执行代码,输出”no”,这个例子充分说明了stdClass类并不是所有类的基类。它仅仅是PHP的一个保留类,或者说一个类似于strlen函数这样的一个角色。 我们从源码的维度看看stdClass类的实现,它注册的位置在 Zend/zend_builtin_functions.c文件中。如下:

ZEND_MINIT_FUNCTION(core) { /* {{{ */
    zend_class_entry class_entry;

    /* 注册stdClass 类 */
    INIT_CLASS_ENTRY(class_entry, "stdClass", NULL);
    zend_standard_class_def = zend_register_internal_class(&class_entry TSRMLS_CC);

    /* 注册默认类,接口,如Exception类,SPL中的一些类等 */
    zend_register_default_classes(TSRMLS_C);    

    return SUCCESS;
}
/* }}} */

这是zend_builtin_module的模块初始化函数,在PHP内核进行模块初始化操作时会自动加载这个函数, 这样,stdClass类的注册操作也就会被执行了。从这段代码可以看出,stdClass类是一个没有成员变量也没有成员方法的类。 它的所有的魔术方法,父类、接口等在初始化时都被设置成NULL。由于在PHP中对于一个类我们无法动态的添加方法, 所以这个类只能用来处理动态属性,这也是我们一种常见的用法。

总结一下:

stdClass类是PHP的一个内部保留类,初始时没有成员变量也没成员方法,所有的魔术方法都被设置为NULL,可以使用其传递变量参数,但是没有可以调用的方法。stdClass类可以被继承,只是这样做没有什么意义。

思考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也有类似的问题。 可以思考一下:各语言这样实现的目的是什么?是否有更好的方案?