动态字段用于扩展性和可升级性

在之前关于动态字段和动态对象字段的课程中,我们讨论了如何动态地向现有对象添加新属性。 这是一种强大的功能,特别是在升级现有的 Move 模块时,可以用来扩展现有对象。

什么是升级?

当 Move 模块部署到 Sui 区块链时,其包会被分配一个地址,如第一课中讨论的那样。 如果我们想添加更多代码并覆盖我们部署到的地址的代码,我们需要升级代码。然而,升级需要遵守特定的规则,这取决于你第一次部署代码时指定的兼容性策略:

Sui 官方文档中的兼容性策略和规则

  1. 不可变:没有人可以升级该包。
  2. 仅依赖性:你只能修改包的依赖项。
  3. 附加性:你可以向包中添加新功能(例如,新公共函数或结构体),但不能更改任何现有功能(例如,不能更改现有公共函数中的代码)。
  4. 兼容性:最宽松的策略。除了更严格的策略允许的操作外,在包的升级版本中:
    • 你可以更改所有函数的实现。
    • 你可以删除函数签名中泛型类型参数的能力约束。
    • 你可以更改、删除或公开任何私有、包级别访问函数和入口函数的签名。
    • 你不能更改公共函数的签名(除了前面提到的能力约束的情况)。
    • 你不能更改现有类型(结构体)。

如你所见,在升级现有代码时,你不能更改任何现有的结构体!这意味着你不能添加新属性或更改现有属性的名称。 动态字段是唯一可以动态扩展现有对象/结构体的方法,因此你只需添加新函数或更新现有函数即可实现这一点。

正如之前课程中讨论的那样,一般不建议添加超过 10 个动态字段,因为它们可能会散布在代码中,难以找到。 有一种很好的方法可以解决这个问题,同时仍然可以轻松扩展现有对象——将新添加的属性分组到一个单独的结构体中:

use sui::dynamic_field;

public struct Laptop has key {
    id: Id,
}

const EXTENSION_1: u64 = 1;

public struct PurchaseDetails has store {
    customer_name: String,
    street_address: String,
    price: u64,
}

public fun add_purchase_details(laptop: &mut Laptop, customer_name: String, street_address: String, price: u64) {
    dynamic_field::add(&mut laptop.id, EXTENSION_1, PurchaseDetails {
        customer_name,
        street_address,
        price,
    });
}

我们将现有的 Laptop 结构体扩展为包含 PurchaseDetails,并添加 3 个新属性。这意味着我们只需要添加一个新的动态字段。请注意:

PurchaseDetails 不是一个对象。我们不打算让它独立存在于 Laptop 之外,因此将其设为对象没有意义。 我们使用扩展号作为键。如果我们需要多次扩展 Laptop 对象,这个扩展号会递增。如果需要,也可以使用字符串或其他类型。 我们还可以使用相同的模式来扩展已作为动态对象字段添加到另一个对象上的对象。

use sui::dynamic_object_field;
use sui::dynamic_field;

public struct Laptop has key {
    id: Id,
}

public struct Sticker has key, store {
    id: Id,
}

const EXTENSION_1: u64 = 1;

public struct StickerPurchaseDetails has store {
    customer_name: String,
    street_address: String,
    price: u64,
}

public fun add_sticker_purchase_details(laptop: &mut Laptop, sticker_name: String, customer_name: String, street_address: String, price: u64) {
    let sticker: &mut Sticker = dynamic_object_field::borrow_mut(laptop, sticker_name);
    dynamic_field::add(&mut sticker.id, EXTENSION_1, StickerPurchaseDetails {
        customer_name,
        street_address,
        price,
    });
}