PHP学习笔记20:预定义接口

PHP学习笔记20:预定义接口

image-20211129162010327

图源:php.net

Traversable

这是一个代表可迭代类型的基本接口,其本身并没有任何方法:

interface Traversable {
}

所以不能直接通过实现该接口来让类具备迭代功能,该接口仅仅用来判断一个类型是否具有迭代能力:

<?php
//数组
$arr = [1, 2, 3];
var_dump($arr instanceof Traversable);
// bool(false)
//生成器
function create_gen(): Generator
{
    yield 0;
    yield 1;
    yield 2;
}
$gen = create_gen();
var_dump($gen instanceof Traversable);
// bool(true)

遗憾的是,在php中数组不能像类一样使用instanceof判别类型,所以尝试通过instanceof Traversable判断数组得到的结果是false。而生成器函数返回的生成器是一个Traversable,原因是生成器Generator实现了Iterator接口,而Iterator接口继承自Traversable接口。

Iterator

Iterator接口代表一个迭代器,继承自Traversable。之前在PHP学习笔记17:迭代器和生成器中已经有过详细说明如何使用此接口实现一个迭代器。

IteratorAggregate

IteratorAggregate(聚合式迭代器)同样继承自Traversable,它只有一个方法,可以返回一个迭代器:

interface IteratorAggregate extends Traversable {
/* 方法 */
public getIterator(): Traversable
}

这个接口的作用类似于Python中的Iterable接口,其作用是可以将迭代器的实现和类本身解耦。

我们看一个示例:

<?php
class Sentence implements Iterator
{
    private array $words;
    private int $index = 0;
    public function __construct(string $string)
    {
        $this->words = str_word_count($string, 1);
    }
    public function current(): mixed
    {
        return $this->words[$this->index];
    }
    public function rewind(): void
    {
        $this->index = 0;
    }
    public function next(): void
    {
        $this->index++;
    }
    public function key(): mixed
    {
        return $this->index;
    }
    public function valid(): bool
    {
        return isset($this->words[$this->index]);
    }
}
$sentence = new Sentence("hello world, how are you!");
foreach ($sentence as $word) {
    echo $word . PHP_EOL;
}
echo PHP_EOL;
// hello
// world
// how
// are
// you

这是在PHP学习笔记17:迭代器和生成器中作为示例的一个类Sentence,它实现了Iterator接口,可以将字符串以单词的方式进行遍历。

这个类的缺点是因为直接实现了Iterator接口,类Sentence和迭代器的实现代码是紧耦合,整个代码显得很臃肿,扩展性也比较差。我们可以利用IteratorAggregage接口来改善这点:

<?php
class Sentence implements IteratorAggregate
{
    private array $words;
    public function __construct(string $string)
    {
        $this->words = str_word_count($string, 1);
    }
    public function getIterator(): Traversable
    {
        return new class($this->words) implements Iterator
        {
            private array $words;
            private int $index = 0;
            public function __construct(array &$words)
            {
                $this->words = &$words;
            }
            public function current(): mixed
            {
                return $this->words[$this->index];
            }
            public function rewind(): void
            {
                $this->index = 0;
            }
            public function next(): void
            {
                $this->index++;
            }
            public function key(): mixed
            {
                return $this->index;
            }
            public function valid(): bool
            {
                return isset($this->words[$this->index]);
            }
        };
    }
}
$sentence = new Sentence("hello world, how are you!");
foreach ($sentence as $word) {
    echo $word . PHP_EOL;
}
echo PHP_EOL;
// hello
// world
// how
// are
// you

这是用匿名类实现的版本,在Java中对此类情况一般还可以使用“内嵌类”来实现,但遗憾的是php并不支持内嵌类。

Throwable

Throwable定义了可以通过throw抛出的类型(ExceptionError):

interface Throwable {
/* 方法 */
public getMessage(): string
public getCode(): int
public getFile(): string
public getLine(): int
public getTrace(): array
public getTraceAsString(): string
public getPrevious(): ?Throwable
abstract public __toString(): string
}

用户代码并不能直接实现Throwable,而应当从Exception继承。

ArrayAccess

ArrayAccess定义了数组访问相关的接口:

interface ArrayAccess {
/* 方法 */
public offsetExists(mixed $offset): bool
public offsetGet(mixed $offset): mixed
public offsetSet(mixed $offset, mixed $value): void
public offsetUnset(mixed $offset): void
}

实现了该接口的类可以使用下标像数组那样访问:

<?php
class Sentence implements ArrayAccess
{
    private array $words;
    public function __construct(string $string)
    {
        $this->words = str_word_count($string, 1);
    }
    public function get_length(): int
    {
        return count($this->words);
    }
    public function offsetExists(mixed $offset): bool
    {
        return isset($this->words[$offset]);
    }
    public function offsetGet(mixed $offset): mixed
    {
        return $this->words[$offset];
    }
    public function offsetSet(mixed $offset, mixed $value): void
    {
        $this->words[$offset] = $value;
    }
    public function offsetUnset(mixed $offset): void
    {
        unset($this->words[$offset]);
    }
}
$sentence = new Sentence("hello world, how are you!");
for ($i = 0; $i < $sentence->get_length(); $i++) {
    echo $sentence[$i] . " ";
}
echo PHP_EOL;
// hello world how are you 

遗憾的是实现了ArrayAccess接口后并不能“自动”实现Traversable接口,也就是说不能使用foreach进行遍历。从这方面讲是不如Python灵活的,类似的情况Python会自动满足。

Serializable

Serializable负责对象的序列化和反序列化:

interface Serializable {
/* 方法 */
public serialize(): ?string
public unserialize(string $data): void
}

PHP学习笔记12:类和对象IV中讨论魔术方法的时候已经涉及这个接口,这里不做赘述。

Closure类

Closure(闭包)是一个类而非接口,php用这个类实现了匿名函数。

final class Closure {
/* 方法 */
private __construct()
public static bind(Closure $closure, ?object $newThis, object|string|null $newScope = "static"): ?Closure
public bindTo(?object $newThis, object|string|null $newScope = "static"): ?Closure
public call(object $newThis, mixed ...$args): mixed
public static fromCallable(callable $callback): Closure
}

关于匿名函数可以阅读PHP学习笔记8:函数中匿名函数的部分。

Generator类

Generator同样是一个类,代表生成器:

final class Generator implements Iterator {
/* 方法 */
public current(): mixed
public getReturn(): mixed
public key(): mixed
public next(): void
public rewind(): void
public send(mixed $value): mixed
public throw(Throwable $exception): mixed
public valid(): bool
public __wakeup(): void
}

生成器不能直接使用new关键字创建,而应当使用生成器函数。更多生成器的内容见PHP学习笔记17:迭代器和生成器

Fliber类

Fliber是php8.1.0新加入的协程的核心机制,可以用于创建协程:

final class Fiber {
/* 方法 */
public start(mixed ...$args): mixed
public resume(mixed $value = null): mixed
public throw(Throwable $exception): mixed
public getReturn(): mixed
public isStarted(): bool
public isSuspended(): bool
public isRunning(): bool
public isTerminated(): bool
public static suspend(mixed $value = null): mixed
public static getCurrent(): ?Fiber
}

Fliber和协程的更多内容可以阅读PHP学习笔记18:协程

WeakReference类

具有自动垃圾回收机制的语言通常使用引用计数来判断一个变量是否可以被回收,这意味着只要变量有至少一个引用,它就不能被回收。但某些情况下我们可能不希望出现类似的情况,比如移动开发中,APP的页面UI和后台服务是相对分离的,页面UI随时可以被系统服务暂停或关闭,这种情况下页面的元素当然要被垃圾回收,但如果后台服务对页面中的组件进行了引用,就会阻止正常的垃圾回收。

这时候就需要使用WeakReference(弱引用)。

弱引用和普通引用的区别在于,虽然前者依然建立了到目标变量的引用关系,可以正常获取到目标变量的值,但这种引用关系并不会影响到垃圾回收。

php的WeakReference类定义为:

final class WeakReference {
/* 方法 */
public __construct()
public static create(object $object): WeakReference
public get(): ?object
}

来看一个示例:

<?php
$obj = new stdClass;
$weakObj = WeakReference::create($obj);
var_dump($weakObj->get());
unset($obj);
var_dump($weakObj->get());
// object(stdClass)#1 (0) {
// }
// NULL

WeakMap类

WeakMap可以简单看做是使用WeakReference作为key的映射。也就是说,使用引用作为WeakMap的key并不会影响到对应变量的引用计数和垃圾回收。

final class WeakMap implements Countable, ArrayAccess, IteratorAggregate {
/* 方法 */
public __construct()
public count(): int
public getIterator(): Iterator
public offsetExists(object $object): bool
public offsetGet(object $object): mixed
public offsetSet(object $object, mixed $value): void
public offsetUnset(object $object): void
}

来看一个示例:

<?php
$map = new WeakMap();
$obj = new stdClass;
$map[$obj] = 1;
echo count($map) . PHP_EOL;
// 1
unset($obj);
echo count($map) . PHP_EOL;
// 0

这里的$map有一个key$obj,是一个stdClass对象的引用。而使用unset($obj)删除唯一“有效”的引用计数引用$obj后,对应stdClass的实例也就会被垃圾回收,此时$map的key$obj将变成一个无效引用,会自动从映射中删除,所以count($map)输出是0。

Stringable

Stringable接口只包含一个方法:

interface Stringable {
/* 方法 */
public __toString(): string
}

比较特别的是,所有实现了__toString魔术方法的类都被看做是实现了Stringable接口:

<?php
class MyClass
{
    public function __toString()
    {
        return __CLASS__;
    }
}
$mc = new MyClass;
var_dump($mc instanceof MyClass);
// bool(true)

这大概是因为Stringable接口本身只是php8.0.0加入的新接口,而__toString方法早已存在许久的缘故。无论如何,还是推荐在php8.0.0以上版本的php中显式实现Stringable接口。

UnitEnum

PHP学习笔记15:枚举中提到过UnitEnum接口:

interface UnitEnum {
/* 方法 */
public static cases(): array
}

所有的Enum都默认实现了这个接口,这种实现是由Zend引擎实现的,而这个接口也并不能由用户直接用来实现或继承。其主要用途是类型检测:

<?php
enum Color
{
    case RED;
    case WHITE;
    case BLACK;
}
var_dump(Color::BLACK instanceof UnitEnum);
// bool(true)

要注意的是,instanceof左侧的操作数必须是一个对象,虽然UnitEnum接口定义的是静态方法,严格意义上来说应当使用Color instanceof UnitEnum来判断,但这样做会产生一个语法错误,所以只能使用枚举对象。

BackedEnum

BackedEnum是回退枚举隐式实现的接口:

interface BackedEnum extends UnitEnum {
/* 方法 */
public static from(string|int $value): static
public static tryFrom(string|int $value): ?static
}

它同样由Zend引擎直接实现,无法被用作类型判断以外的用途。具体的用法在PHP学习笔记15:枚举中有过说明,这里不再重复说明。

Countable

Countable接口包含一个方法,用于返回包含的元素个数:

class Countable {
/* 方法 */
abstract public count(): int
}

Countable属于SPL中的接口,而非直接属于php内核,但在比较新的php版本中,SPL扩展已经被默认启用。

示例:

<?php
class MyClass implements Countable
{
    public function __construct(private array $arr)
    {
    }
    public function count(): int
    {
        return count($this->arr);
    }
}
$mc = new MyClass([1, 2, 3]);
echo count($mc);
// 3

OuterIterator

着同样是一个属于SPL扩展的接口:

interface OuterIterator extends Iterator {
/* 方法 */
public getInnerIterator(): ?Iterator
/* 继承的方法 */
public Iterator::current(): mixed
public Iterator::key(): mixed
public Iterator::next(): void
public Iterator::rewind(): void
public Iterator::valid(): bool
}

这个接口是在Iterator接口的基础上添加了一个返回迭代器的方法。这很像是Python中的Iterator接口。其实更好的做法是让OuterIterator接口同时实现IteratorIteratorAggregate接口:

<?php
interface MyOuterIterator extends Iterator,IteratorAggregate{
}
class MyClass implements MyOuterIterator{
    public function getIterator(): Traversable
    {
        return $this;
    }
    public function current(): mixed
    {
        
    }
    public function next(): void
    {
        
    }
    public function key(): mixed
    {
        
    }
    public function rewind(): void
    {
        
    }
    public function valid(): bool
    {
        
    }
}

至于为什么没有这么做,大概是历史遗留问题?

RecursiveIterator

这同样是一个SPL扩展接口,在Iterator基础上添加了用于递归访问子元素的方法:

interface RecursiveIterator extends Iterator {
/* 方法 */
public getChildren(): ?RecursiveIterator
public hasChildren(): bool
/* 继承的方法 */
public Iterator::current(): mixed
public Iterator::key(): mixed
public Iterator::next(): void
public Iterator::rewind(): void
public Iterator::valid(): bool
}

对于这个接口,我并没有想到相应的示例。

SeekIterator

也属于SPL扩展接口,在Iterator接口基础上添加了一个seek方法:

interface SeekableIterator extends Iterator {
/* 方法 */
public seek(int $offset): void
/* 继承的方法 */
public Iterator::current(): mixed
public Iterator::key(): mixed
public Iterator::next(): void
public Iterator::rewind(): void
public Iterator::valid(): bool
}

官方手册的示例是seek方法可以在游标越界后抛出一个OutOfBoundsException类型的异常来说明遍历已经结束,但我比较疑惑的是Iteratorvalid方法已经可以用来验证是否完成遍历,从功能上看两者是重复的。

最后我话费了“一点”时间绘制了以上接口和类的类图,如果要查看的话可以通过这里下载。

谢谢阅读。

往期内容

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值