PHP面向对象基础知识总结

PHP4之真OO文的作者Johan Persson是PHP中著名的JpGraph图表类库的开发者.
本文是作者对于在PHP4中进行面向对象开发时需要注意的几个小问题的总结.翻译:
Binzy Wu [Mail: Binzy at JustDN dot COM], 水平有限, 欢迎探讨.
2004-2-4简介本文的对象是那些曾使用更加成熟的OO [1] 语言, 如Eiffel,
Java, C# [2] or C++(), 进行开发的朋友(如我自己).
在使用PHP4进行完全的OO开发时有着许多的语义[3]
(semantic)上的陷阱[4].希本文内容可助人避我曾犯之错.引用 VS
拷贝语义这基本上是错误的主要来源(至少对于我来说).即使在PHP的文档中你可以读到PHP4较之引用更多使用拷贝语义(如其他我所知的面向对象语言),
但这仍将使你最后在一些细小之处困扰.接下来的两部分用于阐述二个小的例子,
在这二个例子中拷贝语义也许会令你惊讶.要时刻牢记重要的是一个类的变量不是一个指向类的指针而是实际的类自己本身[5].
大多数问题引发自对于赋值操作符(=)的误解, 即以为是给一个对象一个别名,
而实际上却是一个新的拷贝. 例如假设$myObj是某个类的实例,
并且它有一个Set()方法.
那么下面的代码也许不会像一个C++(或者Java)程序员所期望的那样工作.function
SomeFunction($aObj) { $aObj-Set(10); }…SomeFunction ($myObj);…那么现在,
很容易便会认为该函数所调用的Set()方法会作用于$myObj.
但这是错的!其实发生的是$myObj被拷贝为一个新的,
与原对象一样的拷贝—-参数$aObj. 然后当Set()方法被调用时,
它仅仅作用于本地拷贝而非原参数—-$myObj.在包含直接或间接(如上)赋值操作的地方就会发生各种各样的上述问题.为了函数能像你所期望的那样行动(也许是),
那么你不得不通过修改方法申明来告诉PHP使用引用来传递对象, 如:Function
SomeFunction(&$aObj)如果你再一次尝试上面的代码,
那么你会发现Set()方法将作用于原来的参数上,
因为现在我们在作用中创建了一个$myObj的别名—-$aObj.但是你不得不小心,
因为即使是&操作符也不是在任何时候都能救你,
如下面的举例.从一个引用来获得引用假设有如下代码:$myObject = new
SomeClass();$myRefToObject = &$myObject;
如果我们现在想要一个引用的拷贝(因某些理由), 那么我们要做什么呢?
你可能会由于$myRefToObject已经是引用而试图那么写:$myCopyRefToObject =
$myRefToObject; 正确么? 不! PHP会创建$myRefToObject所引用对象的新拷贝.
如果你想拷贝一个对象的引用, 你不得不这么写:$myCopyRefToObject =
&$myRefToObject; 在与前所述例子相当的C++的例子中,
便会创建一个引用的引用. 与其在PHP中不同.
这是一个经验丰富的C++程序员常会作的直觉假设相反的,
而这会是你的PHP程序中小BUG的来源.请小心由此所产生的间接(传递参数)或直接的问题.我个人所达成的结论,
即最好的避免这些语义陷阱的方法是总是用引用来传递对象或者对象赋值.
这不仅仅改进了运行速度(更少的数据拷贝),
而且可以对像我这样的老狗而言使语义更加可预测.在构造函数中对$this使用引用在一个对象的构造函数里初始化作为其他对象发现者(Observer[6])的对象是一个常见的模式.
下面几行代码便是一个示例:class Bettery{function Bettery() {…};function
AddObserver($method, &$obj){$this-obs[] = array($obj,
&$method)}function Notify(){…}}class Display{function
Display(&$batt){$batt-AddObserver(“BatteryNotify”,$this);}function
BatteryNotify() {…}}但是, 这并不会正常工作,
如果你是这么实例化对象的:$myBattery = new Battery();$myDisplay = new
Display($myBattery);
这么做的错误在于new时在构造函数中使用$this并不会返回同一个对象.
反而会返回最近创建对象的一个拷贝.
即在调用AddObserver()时所传送的对象于原对象不是同一个.
然后当Battery类尝试通知所有它的观察者(Observer)(通过调用他们的Notify方法)时,
它并不会调用我们所创建的Display类而是$this所代表的类(即我们所创建的Display类的拷贝).
因此如果Notify()方法更新了一些实例变量,
并不像我们所设想原Display类会被更新, 因为更新的其实是个拷贝.
为了让它工作, 你必须使构造函数返回同一个对象,
正如与最初$this所象征的那样. 可以通过添加&符号于Display的构造,
如$myDisplay = & new
Display($myBattery);一个直接的结果是任何Display类的Client必须了解Display的实现细节.
事实上, 这会产生一个可能引起争论的问题:
所有对象的构建必须使用额外的&符号. 就我所说的基本上是安全的,
但忽略它可能会在某些时候得到不想要的如上述示例般的作用.
在JpGraph中使用了另一种方法来解决.
即需要使用通过添加一个能安全的使用&$this引用的”Init()”方法的所谓二阶段构造来”new”一个对象(仅仅是因为在构造函数中的$this引用返回对象的一个拷贝而不如所期望的那样执行).
因此上面的例子会如下实现:$myBattery = new Battery();$myDisplay = new
Display();$myDisplay-Init($myBattery);如JPGraph.php中的”LinearScale”类.使用foreach另外一个相似代码却不同结果的问题是”foreach”结构的问题.
研究一下下面的二个循环结构的不同版本.// Version 1foreach( $this-plots as
$p ){ $p-Update();}…// Version 2for( $i=0; $icount($this-plots); ++$i )
{$this-plots[$i]-Update();} 现在是一个价值10美元的问题[7]:
version1==version2么?令人惊讶的答案是:No! 这是细小却是关键的不同.
在Version 1中, Update()方法将作用于”plots[]”数组中对象的副本.
因此数组中原来的对象并不会被更新.在Version
2中Update()方法将如预期的作用于”plots[]”数组中的对象.正如第一部分所陈述的,
这是PHP将对象实例作为对象本身来处理而非作为对象引用的结果.译注:[1].
OO: Object-Oriented, 面向对象.[2]. 原文并无C#,
全因Binzy的个人爱好.[3]. Semantic在本文中被译为”语义”,
如有任何建议请和Binzy联系.[4]. C++中有一本著名的”C++ Gotchas”.[5].
这里的类应该是指Instance, 即实例.[6]. 可参见”[GoF95]”, 即”Design
Patterns”.[7]. 有个挺有趣的关于交易的小故事:有人用60美元买了一匹马,
又以70美元的价钱卖了出去;然后, 他又用80美元把它买回来,
最后以90美元的价钱卖出.在这桩马的交易中, 他? (A)赔了10美元; (B)收支平衡;
赚了10美元;(D)赚了20美元;
(E)赚了30美元.这是美国密执安大学心理学家梅尔和伯克要大学生们计算的一个简单的算术题.结果只有不到40%的大学生能够作出正确答案,
多数人认为只赚了10美元.其实, 问题的条件十分明确, 这是两次交易,
每次都赚10美元,
而很多人却错误地认为当他用80美元买回来时己经亏损了10美元. 有趣的是,
同一问题, 以另一种方式提出来:有一个人用60美元买了一匹白马,
又以70元的值卖出去;然后, 用80美元买了一匹黑马,
又以90美元的值卖出去.在这桩买卖马的交易中,
他____(把同样的五个选择罗列出来).这时, 另一组大学生在回答上述问题时,
结果大家都答对了.

1.类的变量成员叫做“属性”,或者叫“字段”、“特征”,在本文档统一称为“属性”。
2.属性中的变量可以初始化,但是初始化的值必须是常数,这里的常数是指php脚本在编译阶段时就为常数,而不是
在编译阶段之后在运行阶段运算出的常数。
3.在类的成员方法里面,可以通过$this->property(property是属性名字)这种方式来访问类的属性、
方法,但是
要访问类的静态属性或者在静态方法里面却不能使用,而是使用self::$property。
4.在类的非静态方法里面可以使用伪变量$this,这个伪变量是调用该方法的实例化对象引用
5.常量的值必须是一个定值,不允许修改,且不能是变量,类属性或其它操作(如函数调用)的结果。
<?php
class MyClass
{
    const constant = ‘constant value’;
    function showConstant() {
        echo  self::constant . “n”;
    }
}
echo MyClass::constant . “n”;
$n=new MyClass();
$n->showConstant();
?>
6.构造函数的类会在每次创建对象时先调用此方法,所以非常适合在使用对象之前做一些初始化工作。
如果子类中定义了构造函数则不会暗中调用其父类的构造函数。要执行父类的构造函数,需要在子类的构造函数中
澳门新葡萄京娱乐场 ,调用 parent::__construct()。
7.析构函数会在到某个对象的所有引用都被删除或者当对象被显式销毁时执行。
父类的析构函数不会被引擎暗中调用。要执行父类的析构函数,必须在子类的析构函数体中显式调用
parent::__destruct()。
析构函数在脚本关闭时调用,此时所有的头信息已经发出。
试图在析构函数中抛出一个异常会导致致命错误。
8.当扩展一个类,子类就会继承父类的所有公有和保护方法。但是子类的方法会覆盖父类的方法。
9.范围解析操作符(::),可以用于访问静态成员、方法和常量
当在类的外部访问这些静态成员、方法和常量时,必须使用类的名字。
self 和
parent这两个特殊的关键字是用于在类的内部对成员或方法进行访问的。
10.当一个子类覆盖其父类中的方法时,PHP
不会再执行父类中已被覆盖的方法,直到子类中调用这些方法为止。这
种机制也作用于 构造函数和析构函数、重载 及 魔术 函数。
11.静态变量和方法
声明类成员或方法为static,就可以不实例化类而直接访问。不能通过一个对象来访问其中的静态成员(静态方法
除外)。
由于静态方法不需要通过对象即可调用,所以伪变量$this在静态方法中不可用。
静态属性不可以由对象通过->操作符来访问。
用::方式调用一个非静态方法会导致一个E_STRICT级别的错误。
就像其它所有的PHP静态变量一样,静态属性只能被初始化为一个字符值或一个常量,不能使用表达式。
所以你可
以把静态属性初始化为整型或数组,但不能指向另一个变量或函数返回值,也不能指向一个对象。
 
12.如果没有指定“可见性”,属性和方法默认为public。
13.抽象类
抽象类不能直接被实例化,你必须先继承该抽象类,然后再实例化子类。抽象类中
至少要包含一个抽象方法。如果
类方法被声明为抽象的,那么其中就不能包括具体的功能实现。
继承一个抽象类的时候,子类必须实现抽象类中的所有抽象方法;另外,这些方法的可见性
必须和抽象类中一样(
或者更为宽松)。如果抽象类中某个抽象方法被声明为protected,那么子类中实现的方法就应该声明为protected
或者public,而不 能定义为private。
应用示例:
abstract class AbstractClass
{
 // 强制要求子类定义这些方法
    abstract protected function getValue();
    abstract protected function prefixValue($prefix);
    // 普通方法(非抽象方法)
    public function printOut() {
        print $this->getValue() . “n”;
    }
}
class ConcreteClass1 extends AbstractClass
{
    protected function getValue() {
        return “ConcreteClass1”;
    }
    public function prefixValue($prefix) {
        return “{$prefix}ConcreteClass1”;
    }
}
14.使用接口(interface),你可以指定某个类必须实现哪些方法,但不需要定义这些方法的具体内容。
我们可以通过interface来定义一个接口,就像定义一个标准的类一样,但其中定义所有的方法都是空的。
接口中定义的所有方法都必须是public,这是接口的特性。
要实现一个接口,可以使用implements操作符。类中必须实现接口中定义的所有方法,否则
会报一个fatal错误。
如果要实现多个接口,可以用逗号来分隔多个接口的名称。
实现多个接口时,接口中的方法不能有重名。
接口也可以继承,通过使用extends操作符。
接口中也可以定义常量。接口常量和类常量的使用完全相同。
它们都是定值,不能被子类或子接口修改。
应用示例:
//接口定义
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;
    }
}
15.PHP5提供了一种迭代(iteration)对象的功能,就像使用数组那样,可以通过foreach来遍历对象中的属性。
默认情况下,在外部迭代只能得到外部可见的属性的值。
应用示例:
<?php
class MyClass
{
    public $var1 = ‘value 1’;
    public $var2 = ‘value 2’;
    public $var3 = ‘value 3’;
    protected $protected = ‘protected var’;
    private   $private   = ‘private var’;
    function iterateVisible() {
       echo “MyClass::iterateVisible:n”;
       foreach($this as $key => $value) {
           print “$key => $valuen”;
       }
    }
}
$class = new MyClass();
foreach($class as $key => $value) {
    print “$key => $valuen”;
}
echo “n”;

$class->iterateVisible();
?>
16.设计模式
工厂模式(Factory)允许你在代码执行时实例化对象。它之所以被称为工厂模式是因为它负责“生产”对象。工厂
方法的参数是 你要生成的对象对应的类名称。
单例模式(Singleton)用于为一个类生成一个唯一的对象。最常用的地方是数据库连接。
使用单例模式生成一个
对象后,该对象可以被其它众多对象所使用。
应用示例:
<?php
class Example
{
    // 保存类实例在此属性中
    private static $instance;
   
       // 构造方法声明为private,防止直接创建对象
    private function __construct()
    {
        echo ‘I am constructed’;
    }
    // singleton 方法
    public static function singleton()
    {
        if (!isset(self::$instance)) {
            $c = __CLASS__;
            self::$instance = new $c;
        }
        return self::$instance;
    }
   
    // Example类中的普通方法
    public function bark()
    {
        echo ‘Woof!’;
    }
    // 阻止用户复制对象实例
    public function __clone()
    {
        trigger_error(‘Clone is not allowed.’, E_USER_ERROR);
    }
}
?>

这样我们可以得到一个独一无二的Example类的对象。

<?php
// 这个写法会出错,因为构造方法被声明为private
$test = new Example;
// 下面将得到Example类的单例对象
$test = Example::singleton();
$test->bark();
// 复制对象将导致一个E_USER_ERROR.
$test_clone = clone $test;
?>
17.PHP 5 新增了一个 final
关键字。如果父类中的方法被声明为final,则子类无法覆盖该方法;
如果一个类被
声明为final,则不能被继承。
18.对象复制可以通过clone关键字来完成(如果对象中存在__clone()方法,会先被调用)。对象中的
__clone()
方法不能直接调用。
$copy_of_object = clone $object;
当对象被复制后,PHP5会对对象的所有属性执行一个“浅复制”(shallow
copy)。所有的属性中的引用 仍然不
变,指向原来的变量。如果定义了__clone()方法,则新创建的对象(复制生成的对象)中的__clone()方法会被调
用, 可用于修改属性的值(如果有必要的话)。
 
19.对象比较
当使用对比操作符(==)比较两个对象变量时,比较的原则是:如果两个对象的属性和属性值
都相等,而且两个对象
是同一个类的实例,那么这两个对象变量相等。www.2cto.com
而如果使用全等操作符(===),这两个对象变量一定要指向某个类的同一个实例(即同一个对象)。
20.对象和引用
php的引用是别名,就是两个不同的变量名字指向相同的内容。在php5,一个对象变量已经不再保存整个对象的值。
只是保存一个标识符来访问真正的对象内容。
当对象作为参数传递,作为结果返回,或者赋值给另外一个变量,另
外一个变量跟原来的不是引用的关系,只是他们都保存着同一个标识符的拷贝,这个标识符指向同一个对象的真正

发表评论

电子邮件地址不会被公开。 必填项已用*标注