PHP: interface implementálás trait-tel

NEM LEHETSÉGES

Mégis kiválóan használható! :)

Tételezzük fel, hogy van egy ilyesmi interface-ünk:

<?php

namespace System\Collections;

interface IEnumerable {

function Avg($callback = NULL);

function Count($callback = NULL);

function Contains($item, $comparer = NULL);

function FirstOrDefault($callback = NULL);

function GroupBy(\Closure $callback);

function OrderBy(\Closure $callback);

function Sum($callback = NULL);

function ToArray();

function ToList();

function ToLookup(\Closure $keyMaker);

function Where(\Closure $callback);

function Any($callback = NULL);

function Union($enumerable);

function UnionAll($enumerable);

function Take($take);

function SelectMany(\Closure $func);

function Zip($other, \Closure $func);
}

Interface-nek nem kicsi, sőt, az igaziban ennél több is van, .NET programozók már észlelhetik, miről van szó.

Mi a közös ezekben a metódusokban, ha implementáljuk őket? Egyiknek sincs szüksége másra, csupán egy bejárható kollekcióra, valamire, amit végig iterálva döntésre juthatnak. PHP van egy IteratorAggregate interface, ami egyetlen metódussal rendelkezik, az is a dokumentáció szerint csak egy Traversable típusú akármit ad vissza, de tételezzük fel, hogy mindenki tud olvasni, és a metódus neve alapján egy Iterator típust ad vissza minden ilyen elem.

Ami a nagy ötlet, hogy az interface (IEnumerable) megvalósíthatjuk egyetlen trait felhasználásával, nyilván, a kódba nem írhatjuk bele, hogy implements \IteratorAggregate, mert teljesen értelmetlen lenne, ezért tilva is van, de rakhatunk bele valami ilyesmit:

trait TEnumerable {

/* ... */

private function _getSource() {
if (!($this instanceof \IteratorAggregate)) {
throw new \Exception("TEnumerable user class must implement \\IteratorAggregate");
}

return $this->getIterator();
}

}

Semmi extra, csak azt írjuk elő, hogy a trait-et használó osztály implementálja már az IteratorAggregate interface-t, mert arra támaszkodunk mindvégig. Az összes IEnumerable interface által előírt metódust tudjuk implementálni a trait-ben a getSource() meghívásával legyen az a Single() vagy épp a SelectMany().

Amit nyerünk vele, az az, hogy az összes különlegesebb osztály pl. egy ObjectGroup, aminek egy szekvenciáját egy ObjectGroupContainer adja GroupBy() után implementálhatja gyorsan az IEnumerable-t a trait felhasználásával és az IteratorAggregate implementálásával.

<?php

namespace System\Collections;

class ObjectGroupContainer implements \IteratorAggregate, IEnumerable {

use TEnumerable;

protected $_container;

public function __construct(\SplFixedArray $source) {
$this->_container = $source;
}

public function Having(\Closure $callback) {
$callback = function (ObjectGroup $og) use ($callback) {
return $callback($og);
};
$tmp = array();

foreach ($this->_container as $value) {
/* @var $value ObjectGroup */
if ($callback($value)) {
$tmp[] = $value;
}
}

return new ObjectGroupContainer(\SplFixedArray::fromArray($tmp));
}

public function getIterator() {
return $this->_container;
}

}

Látható, hogy nem kellett összetörnünk magunkat az IEnumerable működés elérése miatt, szemfülesek észrevehetik, hogy a Having kódját érdemes volna valami értelmesebb osztállyal megvalósítani, de majd egyszer.

Ami nagyon klasz, hogy a trait-ben implementált metódusokat felülírhatjuk, így pl. létrehozhatunk egy WhereEnumerable : IteratorAggregate, IEnumerable típus, amiben készíthetünk olyan Where metódusimplementációt, ami egy deferred execution-t lehetővé tevő Closure-rel és más típusokkal operál, így pl. 50 egymás utána Where() hívás lemegy egyetlen iterációval. Hasonlót lehet elérni Skip-Then esetén, de ugyanez játszik az OrderBy-ThenBy esetében is.

Nagyon király.

Tovább a fórumba.