PHP4之真OO


Posted in PHP onOctober 09, 2006

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 1
foreach( $this->plots as $p )
{
$p->Update();
}

// Version 2
for( $i=0; $i<count($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美元的值卖出去.在这桩买卖马的交易中, 他____(把同样的五个选择罗列出来).这时, 另一组大学生在回答上述问题时, 结果大家都答对了.

PHP 相关文章推荐
PHP入门速成教程
Mar 19 PHP
FleaPHP的安全设置方法
Sep 15 PHP
PHP 服务器配置(使用Apache及IIS两种方法)
Jun 01 PHP
简单的PHP缓存设计实现代码
Sep 30 PHP
php更新mysql后获取影响的行数发生异常解决方法
Mar 28 PHP
php 去除html标记--strip_tags与htmlspecialchars的区别详解
Jun 26 PHP
php时区转换转换函数
Jan 07 PHP
PHP中使用php://input处理相同name值的表单数据
Feb 03 PHP
php flush无效,IIS7下php实时输出的方法
Aug 25 PHP
PhpStorm本地断点调试的方法步骤
May 21 PHP
php中目录操作opendir()、readdir()及scandir()用法示例
Jun 08 PHP
如何在PHP中生成随机数
Jun 04 PHP
第十一节 重载 [11]
Oct 09 #PHP
第十二节 类的自动加载 [12]
Oct 09 #PHP
第十四节 命名空间 [14]
Oct 09 #PHP
学习使用PHP数组
Oct 09 #PHP
PHP中创建并处理图象
Oct 09 #PHP
如何正确理解PHP的错误信息
Oct 09 #PHP
php+dbfile开发小型留言本
Oct 09 #PHP
You might like
php程序效率优化的一些策略小结
2010/07/17 PHP
PHP下常用正则表达式整理
2010/10/26 PHP
10个php函数实用却不常见
2015/10/13 PHP
PHP版单点登陆实现方案的实例
2016/11/17 PHP
thinkPHP5.0框架独立配置与动态配置方法
2017/03/17 PHP
PHP实现上传图片到数据库并显示输出的方法
2018/05/31 PHP
js查找节点的方法小结
2015/01/13 Javascript
jQuery时间插件jquery.clock.js用法实例(5个示例)
2016/01/14 Javascript
jQuery实现区域打印功能代码详解
2016/06/17 Javascript
AngularJS指令中的绑定策略实例分析
2016/12/14 Javascript
easyui combotree加载静态数据问题(选不上)解决方法
2016/12/26 Javascript
利用Query+bootstrap和js两种方式实现日期选择器
2017/01/10 Javascript
js实现自动轮换选项卡
2017/01/13 Javascript
jquery easyui DataGrid简单示例
2017/01/23 Javascript
jQuery插件实现弹性运动完整示例
2018/07/07 jQuery
Angular6封装http请求的步骤详解
2018/08/13 Javascript
JavaScript中this的全面解析及常见实例
2019/05/14 Javascript
jQuery设置下拉框显示与隐藏效果的方法分析
2019/09/15 jQuery
node 版本切换的实现
2020/02/02 Javascript
JS自定义滚动条效果
2020/03/13 Javascript
[56:20]LGD vs VP Supermajor 败者组决赛 BO3 第三场 6.10
2018/07/04 DOTA
python获取文件路径、文件名、后缀名的实例
2018/04/23 Python
Python 十六进制整数与ASCii编码字符串相互转换方法
2018/07/09 Python
python中字符串内置函数的用法总结
2018/09/13 Python
python实现一个简单的ping工具方法
2019/01/31 Python
Python实现 版本号对比功能的实例代码
2019/04/18 Python
python实现拼接图片
2020/03/23 Python
Django restful framework生成API文档过程详解
2020/11/12 Python
CSS改变网页中鼠标选中文字背景颜色例子
2014/04/23 HTML / CSS
Finishline官网:美国一家领先的运动品牌鞋类、服装零售商
2016/07/20 全球购物
Linux的主要特性
2016/09/03 面试题
应聘自荐书
2013/10/08 职场文书
幼儿园校园小喇叭广播稿
2014/10/17 职场文书
环保建议书作文400字
2015/09/14 职场文书
如何在Python项目中引入日志
2021/05/31 Python
比较几种Redis集群方案
2021/06/21 Redis