复合

想象一下,一个音频系统由单个歌曲和歌曲播放列表组成。是的,播放列表由歌曲组成,但我们希望两者都能单独处理。两者都是音乐类型,都可以播放。

复合设计模式在这方面可以提供帮助;它允许我们忽略对象组合与单个对象之间的差异。它允许我们使用相同或几乎相同的代码来处理两者。

让我们举个小例子:歌曲是我们的 leaf,播放列表是合成物。Music 是我们对播放列表和歌曲的抽象;因此,我们可以称其为我们的组件。所有这一切的客户端就是我们的 index.php 文件。

不区分叶节点和分支,我们的代码就不会那么复杂,也就不容易出错。

让我们先为 Music 定义一个接口:

Unresolved include directive in modules/ROOT/pages/ch04/ch4-05.adoc - include::example$/Chapter 4/Composite/Music.php[]

现在让我们从 Song 类开始整理一些实现:

Unresolved include directive in modules/ROOT/pages/ch04/ch4-05.adoc - include::example$/Chapter 4/Composite/Song.php[]

现在,我们可以开始组建 Playlist 类了。在这个示例中,你可能会注意到我使用了一个名为 spl_object_hash 的函数来设置歌曲数组中的键。在处理对象数组时,这个函数绝对是个好帮手。

这个函数的作用是为每个对象返回一个唯一的哈希值,只要对象不被销毁,无论类的哪些属性发生了变化,这个哈希值都会保持一致。它为任意对象提供了一种稳定的寻址方式。一旦对象被销毁,哈希值就可以重新用于其他对象。

该函数不会对对象的内容进行散列;它只是显示内部句柄和句柄表指针。这意味着如果更改对象的属性,哈希值也不会改变。尽管如此,它并不能保证唯一性。如果一个对象被销毁,随后又立即创建了一个相同类的对象,那么就会得到相同的哈希值,因为在第一个类被取消引用和销毁后,PHP 将重复使用相同的内部句柄。

这将是真实的,因为 PHP 可以使用内部句柄:

var_dump(spl_object_hash(new stdClass()) === spl_object_hash(new stdClass()));

然而,这将是错误的,因为 PHP 必须创建一个新的处理程序:

$object = new StdClass();
var_dump(spl_object_hash($object) === spl_object_hash(new stdClass()));

现在让我们回到 Playlist 类。让我们用它来实现我们的 Music 接口:

Unresolved include directive in modules/ROOT/pages/ch04/ch4-05.adoc - include::example$/Chapter 4/Composite/Playlist.php[]

现在,让我们在 index.php 文件中将这一切整合在一起。我们现在要做的是创建一些歌曲对象,其中一些将使用 addSong 函数分配给播放列表。

由于播放列表的实现方式与歌曲相同,我们甚至可以在其他播放列表中使用 addSong 函数(在这种情况下,我们最好将 addSong 函数重命名为 addMusic)。

我们先播放父播放列表,然后播放子播放列表,进而播放这些播放列表中的所有歌曲:

Unresolved include directive in modules/ROOT/pages/ch04/ch4-05.adoc - include::example$/Chapter 4/Composite/index.php[]

当我们运行这个脚本时,我们可以看到预期的输出:

Playing song #57106d5adb364, Lost In Stereo.
Playing song #57106d5adb63a, Running From Lions.
Playing song #57106d5adb654, Guts.