对象拥有对象

另一种组合对象的方法是让对象拥有其他对象。对于其他三种方法——对象包装、动态字段、动态对象字段,所有权被放弃或设置为隐藏对象(动态字段对象)。在某些情况下,希望父对象拥有子对象(例如,SuiFren 拥有它所戴的帽子)。这可以被视为在复杂应用设计中明确表示对象层次结构的一种方法,其中包含多种类型的对象。

对象拥有对象的另一个有趣用例是可组合的NFT——就像 SuiFren 拥有它们的帽子一样。这允许将 NFT 强有力地组合在一起,使其对用户而言是自然的,这在使用 NFT 的 Web 3 游戏中经常见到。 与其他三种方法相比,对象拥有对象的一个缺点是以后很难移除被拥有的对象。因此,如果关系很少或从未改变,对象拥有对象通常更有用。

public struct Laptop has key {
    id: UID,
}

public struct Sticker has key, store {
    id: UID,
    image_url: String,
}

public fun add_sticker(laptop: &Laptop, sticker: Sticker) {
    transfer::public_transfer(sticker, object::uid_to_address(&laptop.id));
}

在上面的例子中,我们显式地将 Sticker 对象转移到 Laptop。它现在由 Laptop 拥有。为了以后移除它,我们需要使用 Receiving

public fun remove_sticker(laptop: &mut Laptop, sticker: Receiving<Sticker>) {
    let sticker = transfer::public_receive(&mut laptop.id, sticker);
    // 用Sticker做点什么
}

为了提取先前转移到 Laptop 对象中的 Sticker 对象,我们需要 Laptop 的所有者调用 remove_sticker 函数,传入 Laptop 的可变引用和一个类型为 Receiving 的变量。在发送交易时,所有者可以指定 Sticker 对象的地址,Sui VM 将自动将其转换为 Receiving 类型。然后我们可以调用 transfer::public_transfer 来获取底层的 Sticker。如你所见,这比对象包装或动态字段要复杂一些。

请注意,从另一个对象中提取对象也受转移政策的约束(参见前一课程的相应章节):

  1. 如果内部对象具有存储能力,可以调用 transfer::public_receive 将其从定义其结构体的模块之外提取。
  2. 如果内部对象没有存储能力,不能调用 transfer::public_receive。只能在定义其结构体的模块内部调用 transfer::receive