【问题标题】:PHP interfaces IteratorAggregate vs Iterator?PHP接口IteratorAggregate vs Iterator?
【发布时间】:2012-11-17 11:01:07
【问题描述】:

IteratorAggregate 是一个创建外部迭代器的接口

class myData implements IteratorAggregate
{
    public $property1 = "Public property one";
    public $property2 = "Public property two";
    public $property3 = "Public property three";

    public function __construct()
    {
        $this->property4 = "last property";
    }

    public function getIterator()
    {
        return new ArrayIterator($this);
    }
}

$obj = new myData;

您将能够使用foreach 遍历对象:

foreach($obj as $key => $value) {
    var_dump($key, $value);
    echo "\n";
}

虽然Iterator 是一个外部迭代器或可以在内部迭代的对象的接口

class myIterator implements Iterator
{
    private $position = 0;
    private $array = array('one', 'two', 'three');

    function rewind()
    {
        $this->position = 0;
    }

    function current()
    {
        return $this->array[$this->position];
    }

    function key()
    {
        return $this->position;
    }

    function next()
    {
        ++$this->position;
    }

    function valid()
    {
        return isset($this->array[$this->position]);
    }
}

再一次,你可以用基本相同的方式遍历它:

$it = new myIterator;

foreach($it as $key => $value) {
    var_dump($key, $value);
    echo "\n";
}

那么谁能解释一下为什么我们需要两个接口,它们之间有什么区别?

【问题讨论】:

    标签: php arrays iterator traversal


    【解决方案1】:

    我假设IteratorAggregate 适用于您希望节省时间的情况,Iterator 适用于您需要精细控制对象迭代的情况。例如,它允许您在 next()key()prev() 故障上添加自定义异常、缓存(如果您遍历通过网络获取数据的东西)、在返回值之前预处理值。

    【讨论】:

    • 如果你想节省更多时间,你可以简单地yieldgetIterator中的值。
    【解决方案2】:

    IteratorAggregate 是一种实现迭代器的简单方法。缺点是您不能添加next()key() 等方法,因为它们不会在正常的foreach 遍历期间被调用。

    如果您需要实现自定义方法,则需要实现 OuterIterator 或(更容易)扩展 IteratorIterator

    IteratorAggregate 的优势在于速度,它比它的替代品快得多。它的问题在于......尽管它的名称不是迭代器而是可遍历的(同样,没有下一个,键,当前,有效,倒带方法)。

    这是一个简单的基准测试,我在我糟糕的笔记本电脑上运行,IteratorAggregateIteratorIteratorOuterIterator 均使用 iterator_to_arrayforeach 进行了 100 万次迭代: (还要注意 Aggregate 的 next 方法中的 echo:没有打印“x”,证明它从未被调用过)

    $ repeat 3 php benchIterator.php
    ------------------------------------
    Outer_ToArray: 569.61703300476 ms
    Aggregate_ToArray: 552.38103866577 ms
    IteratorIt_ToArray: 546.95200920105 ms
    Outer_Foreach: 1679.8989772797 ms
    IteratorIt_Foreach: 1019.6850299835 ms
    Aggregate_Foreach: 367.35391616821 ms
    ------------------------------------
    Outer_ToArray: 570.75309753418 ms
    Aggregate_ToArray: 544.40784454346 ms
    IteratorIt_ToArray: 555.06300926208 ms
    Outer_Foreach: 1682.2130680084 ms
    IteratorIt_Foreach: 988.21592330933 ms
    Aggregate_Foreach: 356.41598701477 ms
    ------------------------------------
    Outer_ToArray: 566.06101989746 ms
    Aggregate_ToArray: 543.1981086731 ms
    IteratorIt_ToArray: 546.28610610962 ms
    Outer_Foreach: 1663.2289886475 ms
    IteratorIt_Foreach: 995.28503417969 ms
    Aggregate_Foreach: 356.16087913513 ms
    

    这是我用于基准测试的代码:

    <?php
    
    class Aggregate implements \IteratorAggregate
    {
        protected $var;
    
        public function __construct($var = null)
        {
            if (is_array($var)) {
                $this->var = new ArrayIterator($var);
            }
            if ($var instanceof \Traversable) {
                $this->var = $var;
            }
        }
        public function next()
        {
            echo 'x';
        }
        public function toArray()
        {
            return iterator_to_array($this->var, true);
        }
    
        public function getIterator()
        {
            return $this->var;
        }
    
    }
    
    class Outer implements \OuterIterator
    {
        protected $var;
    
        public function __construct($var = null)
        {
            if (is_array($var)) {
                $this->var = new ArrayIterator($var);
            }
            if ($var instanceof \Traversable) {
                $this->var = $var;
            }
        }
    
        public function toArray()
        {
            return iterator_to_array($this->var, true);
        }
    
        public function getInnerIterator()
        {
            return $this->var;
        }
    
        public function current()
        {
            return $this->var->current();
        }
        public function next()
        {
            $this->var->next();
        }
        public function key()
        {
            return  $this->var->key();
        }
        public function valid()
        {
            return  $this->var->valid();
    
        }
        public function rewind()
        {
         $this->var->rewind();
    
        }
    }
    
    
    class IteratorIt extends IteratorIterator
    {
        public function __construct($var = null)
        {
            if (is_array($var)) {
                $var = new ArrayIterator($var);
    
            }
            parent::__construct($var);
    
        }
    
        public function toArray()
        {
            return iterator_to_array($this->getInnerIterator(), true);
        }
        public function getIterator()
        {
            return $this->getInnerIterator();
        }
    }
    
    function bench($name, $test)
    {
        echo "$name: ";
        $start = microtime(true);
        $test();
        $time = microtime(true);
        $time -= $start;
    
        echo ($time * 1000) . ' ms' . PHP_EOL;
    }
    
    $max = 1e6;
    $array = range (1, 1e6);
    $testSuites = [
        'Outer_ToArray' => function () use ($max, $array) {
            $iterator = new Outer($array);
            $r = $iterator->toArray();
        },
        'Aggregate_ToArray' => function () use ($max, $array) {
            $iterator = new Aggregate($array);
            $r = $iterator->toArray();
        },
        'IteratorIt_ToArray' => function () use ($max, $array) {
            $iterator = new IteratorIt($array);
            $r = $iterator->toArray();
        },
        'Outer_Foreach' => function () use ($max, $array) {
            $iterator = new Outer($array);
            foreach ($iterator as $k => $v)
            {
    
            }
        },
        'IteratorIt_Foreach' => function () use ($max, $array) {
            $iterator = new IteratorIt($array);
            foreach ($iterator as $k => $v)
            {
    
            }
        },
        'Aggregate_Foreach' => function () use ($max, $array) {
            $iterator = new Aggregate($array);
            foreach ($iterator as $k => $v)
            {
    
            }
        },
    ];
    
    echo '------------------------------------'.PHP_EOL;
    foreach ($testSuites as $name => $test) {
        bench($name, $test);
    }
    

    【讨论】:

      【解决方案3】:

      它们有什么区别?

      名称的不同(“聚合”)。

      聚合是什么意思?

      聚合状态通过禁止外部对象持有对其成员的引用来保证聚合内所做更改的一致性。 (wikipedia)


      TC 代码:

      class myData implements IteratorAggregate {//your implementation}
      
      class myIterator implements Iterator {//your implementation}
      

      为了清楚起见,这里是聚合代码:

      $renderFunction = function($iterator) {
          foreach($iterator as $key => $value) {
              echo "$key: $value\n";
              foreach($iterator as $key => $value) {
                 echo "    $key: $value\n";
              }
          }
      };
      
      $renderFunction(new myData); // Aggregate on
      $renderFunction(new myIterator); // Aggregate off
      

      你对案件有什么期望?

      https://3v4l.org/JHDNQ

      property1: Public property one
          property1: Public property one
          property2: Public property two
          property3: Public property three
          property4: last property
      property2: Public property two
          property1: Public property one
          property2: Public property two
          property3: Public property three
          property4: last property
      property3: Public property three
          property1: Public property one
          property2: Public property two
          property3: Public property three
          property4: last property
      property4: last property
          property1: Public property one
          property2: Public property two
          property3: Public property three
          property4: last property
      

      0: one
          0: one
          1: two
          2: three
      

      【讨论】:

      • 我很好奇,在您提供的链接中,例如 php7 似乎比 php 5.6 慢。这是正常的吗?或者我根本看不懂数据
      • @Dazag 你是对的,但时间上的差异很小,我不知道是否使用过 opcache....
      • 个人最喜欢这个答案。
      【解决方案4】:

      通过实现IteratorAggregate,我们将实现迭代器函数(key()next()current()valid()rewind())的工作委托给其他类(通过仅实现getIterator()

      这样,它可以帮助我们在 OOP 中实现关注点分离。

      【讨论】:

        【解决方案5】:

        对我来说,迭代器接口使代码更具可读性。如果您的业务逻辑需要更具体的方法,那么您可以实现而不是做您想做的事情。或者直接快速创建空类或实例。主要原因是代码干净。

        【讨论】:

        • 你能解释一下“可读代码”是什么意思吗?这不是接口的用途
        • 我的意思是代码更具可读性,示例在最后一条消息中使用来自接口的短命名方法,而不是使用本机函数。
        • 你能再澄清一下吗?我不明白“短命名方法”和“本机函数”之间的区别
        • 区别在于OOP,更安全和优化。
        【解决方案6】:

        主要取决于您要开发什么,以及其他人是否会使用您的类来开发某些东西。

        Iterator 的简单形式在单级控制循环中的工作方式与IteratorAggregate 一样好,并且大多数情况下是可互换的。

        foreach ($it as $key => $value) {
            // Both examples from the original question will out put the same results.
        }
        

        Iterator 失败的地方是高级逻辑,例如当有多个嵌套循环时。只有一个索引的简单迭代器最终只会循环一次,除了最深的控制循环之外,除非您在每个循环中手动重置它。

        $i = $j = 0;
        foreach ($it as $key => $value) {
            foreach ($it as $key2 => $value2) {
                // Internal index is reset and then incremented as expected.
                $j++;
            }
            // Loop ends after 1 iteration unless you reset internal position.
            # $it->mySetIteratorPosition($i);
            $i++;
        }
        $i === 1;
        $j === count($it);
        

        迭代器没有一个函数来告诉它循环何时中断,因此如果有人在其中一个嵌套循环完成之前中断它,它很容易导致无限循环。

        foreach ($it as $key => $value) {
            foreach ($it as $key2 => $value2) {
                // Internal pointer is reset back to 0
                break;
            }
            // Internal pointer is incremented from 0 to 1
        }
        // Unreachable statement
        

        IteratorAggregate 的工作原理是在新循环开始时创建一个新的Iterator 对象。这意味着嵌套循环更易于维护,并且新的Iterator 对象在循环结束时(或不久之后)被垃圾收集器丢弃。

        如果您想手动控制rewindcurrentnextkeyvalid 的处理,那么您需要做的就是在@987654336 中使用自定义Iterator 类@,或者扩展OuterIterator

        class myData implements IteratorAggregate
        {
            public function getIterator()
            {
                $it = new myIterator($this);
                $it->mySetArray((array) $this);
                return $it;
            }
        }
        

        【讨论】:

        • 这是一个非常重要的区别!
        【解决方案7】:

        那么谁能解释一下为什么我们需要两个接口,它们之间有什么区别?

        PHP 中存在抽象基接口Traversable

        如果一个类实现了Traversable,您可以在foreach loop 中调用它并对其进行迭代。但是,一个类不能直接实现Traversable,因为它是一个抽象基接口。

        相反,一个类可以实现IteratorAggregateIterator。所以它们都有一个共同点,如果你想用foreach loop 使一个对象可调用,它需要实现其中一个(或它们的任何子级)。

        如果一个人想要控制指针并给出精确的指令how,它应该迭代一个实现Iterator。如果想使用在foreach loop 中使用的现有Iterator,则实现IteratorAggregate

        您始终可以使用IteratorIteratorIteratorAggregate 转换回Iterator。 PHP docs 也有一个很好的例子

        【讨论】:

          猜你喜欢
          • 2018-08-15
          • 1970-01-01
          • 2015-10-06
          • 1970-01-01
          • 2015-03-11
          • 2013-03-03
          • 1970-01-01
          • 2012-05-03
          • 2019-01-12
          相关资源
          最近更新 更多