我们都知道如何在 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(), 结语 在开发过程中,我们可以根据实际需要,灵活使用这三种设计模式。
—-
编译者/作者:知行之录
玩币族申明:玩币族作为开放的资讯翻译/分享平台,所提供的所有资讯仅代表作者个人观点,与玩币族平台立场无关,且不构成任何投资理财建议。文章版权归原作者所有。
|