APP
中国版App下载 Android & iPhone
金色专栏
  • 发布文章
  • 发布活动

Rust 初始化结构体的设计模式

我们都知道如何在 Rust 中初始化一个简单的结构体,但面对复杂结构体时,我们应该选择怎样的方式去初始化它呢?在分析 Substrate 代码的过程中,学习到一些复杂结构体初始化的设计模式,本文整理总结如下:

  • new模式

  • builder模式

  • Default模式

new模式

初始化结构体的第一种模式,就是在结构体中使用以下签名声明一个new函数。

pub fn new(param1, param2 : T) -> Self {
   Self {
       // 初始化
   }
}

这种是最常见的方式。它非常简单,对于简单的结构体也很适用。比如在 Substrate 的 primitives/runtime/src/offchain/http.rs 中有如下代码:

pub struct Header {
   name: Vec<u8>,
   value: Vec<u8>,
}

impl Header {
   /// Creates new header given it's name and value.
   pub fn new(name: &str, value: &str) -> Self {
       Header {
           name: name.as_bytes().to_vec(),
           value: value.as_bytes().to_vec(),
       }
   }
}

但是,随着结构体复杂性的增加,它开始出现问题。比如:

impl<B, E, Block, RA> Client<B, E, Block, RA> where
   B: backend::Backend<Block>,
   E: CallExecutor<Block>,
   Block: BlockT,
{
   /// Creates new Substrate Client with given blockchain and code executor.
   pub fn new(
       backend: Arc<B>,
       executor: E,
       build_genesis_storage: &dyn BuildStorage,
       fork_blocks: ForkBlocks<Block>,
       bad_blocks: BadBlocks<Block>,
       execution_extensions: ExecutionExtensions<Block>,
       _prometheus_registry: Option<Registry>,
   ) -> sp_blockchain::Result<Self> {

   }

如果要在其它文件或 crate 中构造此结构体 Client,除非记住 new函数的签名或借助IDE的提示,否则可能会不记得参数列表。而且new模式还有个问题,就是不适合用在供外部使用的API实现上,因为如果结构体增加或减少一个字段,所有调用该new函数的地方都要做相应的修改。

builder模式

上面提到的问题,可以使用builder模式来解决。这是初始化结构体的第二种模式,就是为结构体使用以下签名实现一个build函数。

impl Struct {
   pub fn build(self) -> Struct {
       // 初始化
       Struct {

       }
   }
}

在分析 Substrate 启动流程代码的过程中,有一个很重要的类型ServiceBuilder,通过构建它来启动整个区块链服务所需要的各种组件。代码在client/service/src/builder.rs中:

pub struct ServiceBuilder<TBl, TRtApi, TCl, TFchr, TSc, TImpQu, TFprb, TFpp,
   TExPool, TRpc, Backend>
{
   config: Configuration,
   pub (crate) client: Arc<TCl>,
   backend: Arc<Backend>,
   tasks_builder: TaskManagerBuilder,
   keystore: Arc<RwLock<Keystore>>,
   fetcher: Option<TFchr>,
   select_chain: Option<TSc>,
   pub (crate) import_queue: TImpQu,
   finality_proof_request_builder: Option<TFprb>,
   finality_proof_provider: Option<TFpp>,
   transaction_pool: Arc<TExPool>,
   rpc_extensions: TRpc,
   remote_backend: Option<Arc<dyn RemoteBlockchain<TBl>>>,
   marker: PhantomData<(TBl, TRtApi)>,
   background_tasks: Vec<(&'static str, BackgroundTask)>,
}

impl ServiceBuilder<(), (), (), (), (), (), (), (), (), (), ()> {

   /// Start the service builder with a configuration.
   pub fn new_light<TBl: BlockT, TRtApi, TExecDisp: NativeExecutionDispatch + 'static>(
       config: Configuration,
   ) -> Result<ServiceBuilder<
       ...
   >, Error> {
       ...
       Ok(ServiceBuilder {
           ...
       })
   }
}

impl<TBl, TRtApi, TBackend, TExec, TSc, TImpQu, TExPool, TRpc>
ServiceBuilder<
   ...
> where
   ...
{
   /// Builds the service.
   pub fn build(self) -> Result<Service<
       ...
   >, Error>
       where TExec: CallExecutor<TBl, Backend = TBackend>,
   {
       ...
       Ok(Service {
           ...
       })
   }
}

build函数的签名可以看出builder模式不需要指定所有内容来构建结构体ServiceBuilder。我们看如何使用它,代码在bin/node/cli/src/service.rs中:

/// Builds a new service for a light client.
pub fn new_light(config: Configuration)
-> Result<impl AbstractService, ServiceError> {
   type RpcExtension = jsonrpc_core::IoHandler<sc_rpc::Metadata>;
   let inherent_data_providers = InherentDataProviders::new();

   let service = ServiceBuilder::new_light::<Block, RuntimeApi, node_executor::Executor>(config)?
       .with_select_chain(|_config, backend| {
           Ok(LongestChain::new(backend.clone()))
       })?
       ...
       .build()?;

   Ok(service)
}

由于这个结构体相当的复杂,它的构建方法build函数有503行。这说明了builder模式的一个大缺点:非常长。行数是new模式的几倍。

Default模式

这是初始化结构体的第三种模式,就是先为结构体实现Default,实现default函数,然后再为其实现一个类似build的函数。

impl Default for Struct {
   fn default() -> Self {
       // 初始化部分
   }
}

impl Struct {
   pub fn build(self) -> Struct {
       // 初始化
       Struct {

       }
   }
}

在 Substrate 中有个投票规则的构建器VotingRulesBuilder,它使用一组规则来逐步约束投票。代码在client/finality-grandpa/src/voting_rule.rs中:

pub struct VotingRulesBuilder<Block, B> {
   rules: Vec<Box<dyn VotingRule<Block, B>>>,
}

impl<Block, B> Default for VotingRulesBuilder<Block, B> where
   Block: BlockT,
   B: HeaderBackend<Block>,
{
   fn default() -> Self {
       VotingRulesBuilder::new()
           .add(BeforeBestBlockBy(2.into()))
           .add(ThreeQuartersOfTheUnfinalizedChain)
   }
}

impl<Block, B> VotingRulesBuilder<Block, B> where
   Block: BlockT,
   B: HeaderBackend<Block>,
{
   /// Return a new `VotingRule` that applies all of the previously added
   /// voting rules in-order.
   pub fn build(self) -> impl VotingRule<Block, B> + Clone {
       VotingRules {
           rules: Arc::new(self.rules),
       }
   }
}

这段代码看起来非常类似于builder模式,但是与其相比,我们大大降低了build代码的长度。如果需要进行一些默认的操作,则可以在default()函数中进行。关于使用,我们可以在bin/node/cli/src/service.rs中看到如下的代码:

voting_rule: grandpa::VotingRulesBuilder::default().build(),

结语

在开发过程中,我们可以根据实际需要,灵活使用这三种设计模式。

    jinse.com
    好文章,需要你的鼓励
    jinse.com
    好文章,需要你的鼓励
    了解更多区块链一线报道,与作者、读者更深入探讨、交流,欢迎添加小助手微信:jinsecaijing666, 进入[金色财经读者交流群]。
    发表评论
    0/140
    发布评论
    评论
    文章作者: / 责任编辑: 我要纠错

    声明:本文由入驻金色财经的作者撰写,观点仅代表作者本人,绝不代表金色财经赞同其观点或证实其描述。

    提示:投资有风险,入市须谨慎。本资讯不作为投资理财建议。

    金色财经 > 区块链 > Rust 初始化结构体的设计模式