首 页   · 站长博客 · 用户注册 · 会员登陆  · 会员排行  ·最新主题  ·最近回复  精华区  版权声明  ·论坛管理
  当前登录身份:游客,请先登录。  笔名: 口令: 验证码:   
楼 主  index »  PHP相关资源下载区 » 《PHP设计模式介绍》第八章 迭代器模式  


  作者:lxshark
  注册时间:2009-08-06
  主题/回复:49/50
  积分:639
  等级:★★★(六级)
  称号:声名鹊起

  lxshark@yeah.net..
  593993745
  hi.baidu.com/mythicsky

 

 发表:2009-08-08 20:49:10 阅读 2031 次 回复 1 次 得分0  |   字号 字色
《PHP设计模式介绍》第八章 迭代器模式
类中的面向对象编程封装应用逻辑。类,就是实例化的对象,每个单独的对象都有一个特定的身份和状态。单独的对象是一种组织代码的有用方法,但通常你会处理一组对象或者集合。

属性来自 SQL 查询的一组数据就是一个集合,就像本书前面章节介绍的 Monopoly 游戏示例的对象列表。

集合不一定是均一的。图形用户界面框架中的 Window 对象可以收集任意数量的控制对象 - Menu、Slider 和 Button。并且,集合的实现可以有多种方式:PHP 数字是一个集合,但也是一个散列表,一个链接列表,一个堆栈以及队列。


问题

如何操纵任意的对象集合?

解决方案

使用迭代器模式来提供对集合内容的统一存取。

你可能没有意识到这一点,但你每天都在使用迭代器模式 - 它潜藏在 PHP 的数组类型和各种数组操作函数中。(其实,给你一些固有类的数组的组合和一群用这些固有类工作的可变函数,你将不得不使用这些数组来处理对象集合。这是在 PHP 中的本地数组迭代:


$test = array(‘one’, ‘two’, ‘three’);
$output = ‘’; reset($test); 
do {
$output .= current($test);
} while (next($test));
echo $output; // produces ‘onetwothree’ 


reset() 函数将迭代重新转到数组的开始;current() 返回当前元素的值;next() 则前进至数组中的下一个元素并返回新的 current() 值。当你超出数组的最后一个元素时,next() 返回 false。使用这些迭代方法,PHP 数组的内部实现就与你不相关了。迭代器结合了封装和多态的面向对象程序设计原理。使用迭代器,你可以对集合中的对象进行操作,而无需专门了解集合如何显现或者集合包含什么(对象的种类)。迭代器提供了不同固定迭代实现的统一接口,它完全包含了如何操纵特定集合的详细信息,包括显示哪些项(过滤)及其显示顺序(排序)。

让我们创建一个简单的对象,在数组中对它进行操作。(尽管该示例在 PHP5 环境下,但迭代器并不特定于 PHP5。虽然添加了较多的引用操作符,本章节中的大多数示例在 PHP4 下也能够运行)。对象 Lendable 表示诸如电影、相册等媒体,它作为 web 站点的一部分或服务,允许用户浏览或将他们的媒体集合分享给其他用户。(对 于该示例,请无需考虑其他方面。)让我们开始下面对 Lendable 基础设计的测试。


// PHP5
class LendableTestCase extends UnitTestCase {
function TestCheckout() {
$item = new Lendable;
$this->assertFalse($item->borrower);
$item->checkout(‘John’);
$this->assertEqual(‘borrowed’, $item->status);
$this->assertEqual(‘John’, $item->borrower);
}
function TestCheckin() {
$item = new Lendable;
$item->checkout(‘John’);
$item->checkin();
$this->assertEqual(‘library’, $item->status);
$this->assertFalse($item->borrower);
}



要实现这一最初测试的需求,我们来创建一个带有若干公共属性和一些方法的类,
来触发这些属性的值:


class Lendable {
public $status = ‘library’;
public $borrower = ‘’;
public function checkout($borrower) {
$this->status = ‘borrowed’;
$this->borrower = $borrower;
}
public function checkin() {
$this->status = ‘library’;
$this->borrower = ‘’;
}



Lendable 是一个好的,普通的开端。让我们将它扩展到诸如 DVD 或 CD 的磁道项。媒体扩展了 Lendable,并且磁道详细记录了特定媒体的详细信息,包括项目的名称,发布的年份以及项本身的类型:


class Media extends Lendable {
public $name; public $type; public $year;
public function __construct($name, $year, $type=’dvd’ ) {
$this->name = $name;
$this->type = $type;
$this->year = (int)$year;
}



要使事情更加简单,媒体有三个公共的实例变量,Media::name,Media::year 和Media::type。构造函数采用了两个参数,将第一个存储在 $name 中,第二个存储在 $year 中。构造函数还允许可选的第三个参数来指定类型(缺省为dvd)。

给定单独的对象来操作,你现在可以创建一个容器来包含他们:Library。类似于常用的库,Library 应该能够添加,删除和计算集合中的项。甚至,Library 还应该允许访问集合(本章中的样本代码部分可看到示例)中的单一的项(对象)。

我们开始构建 Library 的测试用例。


class LibraryTestCase extends UnitTestCase {
function TestCount() {
$lib = new Library;
$this->assertEqual(0, $lib->count());
}



它是满足这一测试的简单类:


class Library {
function count() {
return 0;
}




继续将一些有趣的功能添加到测试中:


class LibraryTestCase extends UnitTestCase {
function TestCount() { /* ... */ }
function TestAdd() {
$lib = new Library;
$lib->add(‘one’);
$this->assertEqual(1, $lib->count());
}



实现 add() 的简单方法是建立在 PHP 灵活数组函数的基础上:你可以将项添加到实例变量并使用 count() 来返回集合众项的数量。



class Library {
protected $collection = array();
function count() {
return count($this->collection);
}
function add($item) {
$this->collection[] = $item;
}




Library 现在是一个集合,但它没有提供检索或操纵单一数组成员的方法。

我们回到本章的重点,迭代器设计模式的实现。下列 UML 类图显示了 GoF 迭代器模式与 Media 和 Library 类结合使用巩固示例的方法。

你的集合类必须提供 Factory(参见第 3 章)来创建迭代器的实例。


迭代器类定义 first() 转到集合开始的接口,

next() 移到序列中的下一个项作为你的循环,currentItem() 从集合检索当前的项作为你的循环, isDone() 用于指出你在整个集合中循环结束的时间。

在“示例代码”部分,LibraryGofIterator 类是一个直接实现 GoF 迭代器设计模式的示例。

 


样本代码

在 Library 内实现 GoF 迭代器模式的第一步是为新的具体迭代器写一个新的测试用例。因为每一种测试方法都将操纵包含 Media 实例的 Library,你可以清空 UnitTestCase::setUp() 方法,从而在每种测试的已知状态下将变量填充到 Library 中。

首先,将 Library::getIterator() 方法作为LibraryGofIterator 类的 一个 Factory 实例。


class IteratorTestCase extends UnitTestCase {
protected $lib;
function setup() {
$this->lib = new Library;
$this->lib->add(new Media(‘name1’, 2000));
$this->lib->add(new Media(‘name2’, 2002));
$this->lib->add(new Media(‘name3’, 2001));
}
function TestGetGofIterator() {
$this->assertIsA($it = $this->lib->getIterator()
,’LibraryGofIterator’);
}




实现:

class Library {
// ...
function getIterator() {
return new LibraryGofIterator($this->collection);
}




getIterator() 方法将 Library 的 $collection 传递给新的具体迭代器结构。这一方法有两个重要的实现:每个迭代器都是独立的,因此可以同时操作多个迭代器。另外,迭代器在数组上的操作是当迭代器被请求时才执行的。如果之后将另一个项添加到集合中,你必须请求另一个迭代器来显示它(至少是在该实现中)。让我们通过将声明添加到    TestGetGofIterator() 方法以匹配迭代器设计模式,继续对测试进行加强。

如果你已经对整个集合进行遍历,则 isDone() 方法只应该为 true。如果 iterator 刚刚创建,则 isDone() 显然返回 false,从而指出集合可以遍历。


class IteratorTestCase extends UnitTestCase {
function setup() { /* ... */ }
function TestGetGofIterator() {
$this->assertIsA($it = $this->lib->getIterator()
,’LibraryGofIterator’);
$this->assertFalse($it->isdone());
}




与 TDD 一样,尽可能实现最简单的代码来满足你的测试用例:


class LibraryGofIterator {
function isDone() {
return false;
}




因此,在第一个迭代器间,应该发生什么呢? currentItem() 应该返回第一个 Media 对象,这个对象是在 IteratorTestCase::setUp() 方法中添加的,isDone() 应该继续为 false,因为另两个项仍然等待遍历。


class IteratorTestCase extends UnitTestCase {
function setup() { /* ... */ }
function TestGetGofIterator() {
$this->assertIsA($it = $this->lib->getIterator()
,’LibraryGofIterator’);
$this->assertFalse($it->isdone());
$this->assertIsA($first = $it->currentItem(), ‘Media’);
$this->assertEqual(‘name1’, $first->name);
$this->assertFalse($it->isdone());
}





LibraryGofIterator 接收了构造函数中的 $collection, 这一点非常重要(参见上面的 Library 最小化实现)并从 currentItem() 方法返回 current() 项。


class LibraryGofIterator {
protected $collection;
function __construct($collection) {
$this->collection = $collection;
}
function currentItem() {
return current($this->collection);
}
function isDone() {
return false;
}





在下一个迭代会出现什么? next() 方法应该更改currentItem() 方法返回的项。下面的测试捕获了所期望的行为:


class IteratorTestCase extends UnitTestCase {
function setup() { /* ... */ }
function TestGetGofIterator() {
$this->assertIsA($it = $this->lib->getIterator(), ‘LibraryGofIterator’);
$this->assertFalse($it->isdone());
$this->assertIsA($first = $it->currentItem(), ‘Media’);
$this->assertEqual(‘name1’, $first->name);
$this->assertFalse($it->isdone());
$this->assertTrue($it->next());
$this->assertIsA($second = $it->currentItem(), ‘Media’);
$this->assertEqual(‘name2’, $second->name);
$this->assertFalse($it->isdone());
}




重新建立在 PHP 的数组函数之上,在数组上使用 next():


class LibraryGofIterator {
protected $collection;
function __construct($collection) {
$this->collection = $collection;
}
function currentItem() {
return current($this->collection);
}
function next() {
return next($this->collection);
}
function isDone() {
return false;
}





除了 isDone() 方法必须返回 之外,第三个迭代看起来很像其他的迭代。你还希望 next() 能够成功移到下一个迭代:


class IteratorTestCase extends UnitTestCase {
function setup() { /* ... */ }
function TestGetGofIterator() {
$this->assertIsA($it = $this->lib->getIterator(), ‘LibraryGofIterator’);
$this->assertFalse($it->isdone());
$this->assertIsA($first = $it->currentItem(), ‘Media’);
$this->assertEqual(‘name1’, $first->name);
$this->assertFalse($it->isdone());
$this->assertTrue($it->next());
$this->assertIsA($second = $it->currentItem(), ‘Media’);
$this->assertEqual(‘name2’, $second->name);
$this->assertFalse($it->isdone());
$this->assertTrue($it->next());
$this->assertIsA($third = $it->currentItem(), ‘Media’);
$this->assertEqual(‘name3’, $third->name);
$this->assertFalse($it->next());
$this->assertTrue($it->isdone());
}




对 next() 和 isDone() 方法稍加修改,所有的测试都通过了。代码如下:


class LibraryGofIterator {
protected $collection;
function __construct($collection) {
$this->collection = $collection;
}
function first() {
reset($this->collection);
}
function next() {
return (false !== next($this->collection));
}
function isDone() {
return (false === current($this->collection));
}
function currentItem() {
return current($this->collection);
}




迭代器测试用例只存在一个问题:它没有反映迭代器的典型用法。是的,它测试了迭代器模式的所有功能,但应用程序需要采用更简单的方法来使用迭代器。因此,下一步是使用更贴实际的代码来编写测试。



class IteratorTestCase extends UnitTestCase {
protected $lib;
function setup() { /* ... */ }
function TestGetGofIterator() { /* ... */ }
function TestGofIteratorUsage() {
$output = ‘’;
for ($it=$this->lib->getIterator(); !$it->isDone(); $it->next()){
$output .= $it->currentItem()->name;
}
$this->assertEqual(‘name1name2name3’, $output);
}




 
 
 1#楼  
 
  回复人:lxshark
  注册时间:2009-08-06
  主题/回复:49/50
  积分:639
  等级:★★★(六级)
  称号:声名鹊起

   
 1#楼 发表于2009-08-08 21:35:29  评分:× 

回复给楼主(lxshark)
希望能对你们更好的学习PHP有所帮助,也期望大家有时间多光顾我的
点此打开链接:http://hi.baidu.com/mythicsky
  页数1/1首页 « 1 » 末页
  发表回复:您还没有登陆,无法发表回复。请先[登陆]

一起PHP技术联盟 主办:一起PHP 联系方式:站长QQ:4304410 QQ群:8423742 20159565 站长博客 E-mail: nqp@nqp.me 执行时间:0.019sec
SimsmaBBS 2008 (v6.0) Developed by 17php.com,Copyright(C)2003-2010 All rights reserved. 副本授权:一起PHP官方专用版