建站学 - 轻松建站从此开始!

建站学-个人建站指南,网页制作,网站设计,网站制作教程

当前位置: 建站学 > 网站开发 > PHP教程 >

PHP V5.3 用延后静态绑定(LSB)搞活面向对象(OOP)编程(2)

时间:2011-03-27 11:15来源: 作者: 点击:
为了支持多个类型的单例类,ConnPool 类添加了一个 $klass 静态变量并假设它会在子类中被覆盖。 ConnPoolAS400 子类扩展了 ConnPool 类并提供了 $klass 属性自己的版本。 我们的预期是当 ConnPoolAS400 类的实例创

为了支持多个类型的单例类,ConnPool 类添加了一个 $klass 静态变量并假设它会在子类中被覆盖。 ConnPoolAS400 子类扩展了 ConnPool 类并提供了 $klass 属性自己的版本。 我们的预期是当 ConnPoolAS400 类的实例创建时,$klass 属性会保存 ConnPoolAS400。但是当执行这些代码时,它不会按预期的那样运行。当 PHP 实用函数 get_class 返回 ConnPool 而不是 ConnPoolAS400 时,代码底部的声明会失败。 问题是 ConnPool 类的 getInstance 方法使用的是它自己的 $klass 属性版而非 ConnPoolAS400 的覆盖版。

 清单 2 内的代码存在的问题是 self 关键字绑定到了在编译时引用的属性或方法。self 关键字指向的是包含类,且不会意识到子类。基本上,编译器会用所包含类的名称替换 self 关键字。这就类似于如下这行代码:

self::$onlyOne = new self::$klass();

被编译器替代为:

ConnPool::$onlyOne = new ConnPool::$klass();

而这就是所谓的提前绑定。而您所需要的是 延后绑定。

有了 LSB 的单例继承

您可以通过使用 PHP V5.3 的 LSB 功能修复这个单例功能。可以用 static 替换 self 指定符 self::$onlyOne = new self::$klass();:

self::$onlyOne = new static::$klass();

代码的重新运行的结果是一个成功的声明。

static 关键字会在可能的最近时刻强迫 PHP 绑定到代码实现。没有 LSB, self::$klass 会引用所找到的第一块代码:父类的版本。

PHP V5.3 内一个名为 get_called_class 的新功能稍稍简化了单例的代码。清单 3 用 get_called_class 函数替换了静态 $klass 属性的使用。

清单 3. 用 get_called_class 简化的单例

<?php
class ConnPool {
private static $instance;
public function get_instance() {
if (!is_object(self::$instance)) {
$klass = get_called_class();
self::$instance = new $klass();
}
return self::$instance;
}
}

class ConnPoolAS400 extends ConnPool {}
$db = ConnPoolAS400::get_instance();
assert ('ConnPoolAS400' == get_class($db));
?>
清单 3 内的单例显然更为准确,但更为重要的一点是它使用了 PHP 的 LSB 来引用适当的覆盖静态类。尽管单例实现使用的是子类的类名,其他的模式(比如稍后介绍的活动记录模式)需要引用其他的静态属性。此外,LSB 可同时使用静态函数 和静态属性。静态函数与静态属性一样,作用域也是类而非该类的对象实例。清单 4 显示了使用方法而非属性来指定适当类的单例。

清单 4. 在方法上使用了 LSB 的一个单例

<?php
class ConnPool {
private static $onlyOne;
protected static function getClass() {
return __CLASS__;
}

public function get_instance() {
if (!is_object(self::$onlyOne)) {
$klass = static::getClass();
self::$onlyOne = new $klass();
}
return self::$onlyOne;
}
}

class ConnPoolAS400 extends ConnPool {
protected static function getClass() {
return __CLASS__;
}
}
$db = ConnPoolAS400::get_instance();
assert ('ConnPoolAS400' == get_class($db));
?>
在此代码中,静态 getClass 实现在 ConnPool 内定义并在 ConnPool400 内覆盖。 ConnPool 的 get_instance 方法的如下代码行会在运行时调用适当的方法:

$klass = static::getClass();

活动记录

让我们先来看看活动记录设计模式的一个简单的部分实现。清单 5 显示了一个名为 ActiveRecord 的抽象类以及两个子类:Customer 和 Sales。子类是域类,因为它们向存在于应用程序域内的实用工具提供了包装程序。

清单 5. 活动记录设计模式的简单实现

<?php
abstract class ActiveRecord {
protected static $table;
protected $fieldvalues;
public $select; // used for illustration only

static function findById($id) {
$query = "select * from "
.static::$table
." where id=$id";
return self::createDomain($query);
}
function __get($fieldname) {
return $this->fieldvalues[$fieldname];
}
static function __callStatic($method, $args) {
$field = preg_replace('/^findBy(\w*)$/', '${1}', $method);
$query = "select * from "
.static::$table
." where $field='$args[0]'";
return self::createDomain($query);
}
// TODO: code a __set method
private static function createDomain($query) {
$klass = get_called_class();
$domain = new $klass();
$domain->fieldvalues = array();
$domain->select = $query;
foreach($klass::$fields as $field => $type) {
$domain->fieldvalues[$field] = 'TODO: set from sql result';
}
return $domain;
}
// TODO: code static create, update, delete methods
}
class Customer extends ActiveRecord {
protected static $table = 'custdb';
protected static $fields = array(
'id' => 'int',
'email' => 'varchar',
'lastname' => 'varchar'
);
}
class Sales extends ActiveRecord {
protected static $table = 'salesdb';
protected static $fields = array(
'id' => 'int',
'item' => 'varchar',
'qty' => 'int'
);
}

assert ("select * from custdb where id=123" ==
Customer::findById(123)->select);
assert ("TODO: set from sql result" ==
Customer::findById(123)->email);
assert ("select * from salesdb where id=321" ==
Sales::findById(321)->select);
assert ("select * from custdb where Lastname='Denoncourt'" ==
Customer::findByLastname('Denoncourt')->select);
?>
ActiveRecord 类使用 abstract 修饰符来确保代码不会实例化一个 ActiveRecord 对象。如果用 new ActiveRecord(); 尝试创建一个 ActiveRecord,将会收到一个错误,称 “PHP Fatal error: Cannot instantiate abstract class ActiveRecord”。这是一件好事,因为在没有子类时,ActiveRecord 类不会做任何有价值的事情。

ActiveRecord 类定义一个静态的 $table 变量,它会相继被 Customer 和 Sales 子类覆盖来指定 SQL 表名 custdb 和 salesdb。

ActiveRecord 的静态 findById 函数是活动记录设计模式的实现内常见的一个方法的例子。findById 负责基于所传递的惟一标识符来检索数据库内的适当行,然后再构建并返回代表业务实体的域对象。 findById 方法使用 static 关键字来启用对子类的表名的延后绑定引用。此方法构建一个 SQL select,然后会将域的创建延迟到 createDomain 方法。

createDomain 方法使用子类的名称(通过 PHP V5.3 的 get_called_class 函数)来实例化适当的类。createDomain 方法之后会创建一个数组来保存数据库列的名称和值的一个映射。为了让这个示例尽量简单,ActiveRecord 并不会实际运行 SQL 代码。同时为了让本文的代码能够充分展示并测试 SQL select 的构造,ActiveRecord 具有一个在 createDomain 内设置的 $select 属性。foreach 语句,不是设置来自 SQL 结果集的域属性值,而是将字符串 TODO: set from sql result 填塞到字段值数组的每个元素。这个方法会返回新构建的域,而这个域又会由 findById 方法返回。代码底部四个声明中的第一个声明会验证适当的 SQL 语句是否被创建。

动态属性和 __get

您可能会注意到的 Customer 和 Sales 域名类的一个奇怪的事情是它们并没有任何的域属性。您可能会期望看到如下这些行,作为 Customer 类的属性:

$id;

$email;

$name;

这样一来,您就可以使用以下代码访问这些域属性:

$custObj->id;

$custObj->email;

$custObj->lastname;

(责任编辑:admin)

织梦二维码生成器
顶一下
(0)
0%
踩一下
(0)
0%
------分隔线----------------------------
发表评论
请自觉遵守互联网相关的政策法规,严禁发布色情、暴力、反动的言论。
评价:
表情:
用户名: 验证码:点击我更换图片