Rust中实例化动态对象的示例详解
<div id="navCategory"><h5 class="catalogue">目录</h5><ul class="first_class_ul"><li>序列化serde</li><li>动态类型匹配</li><li>Trait 特质</li><li>From/Into 类型强转</li><li>HashMap映射类型</li><li>最后</li></ul></div><p>在功能开发中,动态创建或获取某个对象的情况很多。在前端JS开发中,可以使用工厂函数,通过给定的类型标识创建不同的对象实例;还可以通过对象映射来实现动态创建对象。</p><p>在<code>Rust</code>中,我们也可以使用这两种方式去创建对象实例,但实现书写的方式可能略有不同;<code>rust</code>还可以通过序列化<code>JSON</code>数据时进行枚举类型匹配。</p>
<p>我们定义好需要测试的数据结构体、方法。小狗、小猫有自己的字段、方法,它们有相同的字段<code>name</code>,也有相同的方法<code>say</code>。</p>
<div class="jb51code"><pre class="brush:plain;">use serde_derive::{Deserialize, Serialize};
#
struct Dog {
name: String,
work: String,
}
impl Dog {
fn new(name: String, work: String) -> Dog {
Dog { name, work }
}
fn say(&self) {
println!("{} say wangwang", self.name);
}
}
#
struct Cat {
name: String,
age: i32,
}
impl Cat {
fn new(name: String) -> Cat {
Cat { name: name, age: 0 }
}
fn say(&self) {
println!("{} say miamiamia", self.name);
}
}
</pre></div>
<p class="maodian"></p><h2>序列化serde</h2>
<p>我们在拿到<code>JSON</code>格式数据进行序列化时,在<code>rust</code>中是需要确定具体数据类型的,但是我们并不知道具体类型,因为现在有两种类型,要合为一种类型,就需要归集,使用枚举<code>enum</code>来定义可能的类型:</p>
<div class="jb51code"><pre class="brush:plain;">#
#
enum Animal {
Dog(Dog),
Cat(Cat),
}
</pre></div>
<p>对于<code>JSON</code>格式和<code>rust</code> 结构体的互相转换,可以使用<code>serde</code>库。也正好利用<code>JSON</code>转结构体这一过程,利用转换机制来实现动态创建对象。</p>
<p>安装相关的库:</p>
<div class="jb51code"><pre class="brush:bash;">cargo add serde serde_derive serde_json
</pre></div>
<p>我们定义一个JSON格式数据,使用<code>serde</code>库进行反序列化,并使用<code>match</code>进行匹配:</p>
<div class="jb51code"><pre class="brush:plain;">fn main() {
let data = r#"
{
"name":"admin",
"age":2
}
"#;
let animal = serde_json::from_str(data).unwrap();
match animal {
Animal::Dog(dog) => {
dog.say();
}
Animal::Cat(cat) => {
cat.say();
}
};
}
</pre></div>
<p>测试运行,正常输出了<code>cat say wangwang</code>。我们修改JSON格式数据</p>
<div class="jb51code"><pre class="brush:plain;">let data = r#"
{
"name":"admin",
"work":"play"
}
"#;
</pre></div>
<p>测试运行,正常输出了<code>dog say wangwang</code>,说明没有逻辑没有问题。</p>
<p>待优化的地方在于我们使用了<code>match</code>,如果我们需要在多个地方使用<code>animal</code>,那么这段匹配逻辑就无处不在了。当有很多方法时,无法控制具体调用哪个方法,就需要不停的去匹配。</p>
<p>我们可以将它们需要调用公共方法在枚举类型<code>Animal</code>定义一下,内部逻辑根据不同类型在调用各自的方法。</p>
<div class="jb51code"><pre class="brush:plain;">impl Animal {
fn say(&self) {
match self {
Animal::Cat(cat) => cat.say(),
Animal::Dog(dog) => dog.say(),
}
}
}</pre></div>
<p>为<code>Animal</code>定义公共方法<code>say</code>,然后在序列化JSON数据格式时,我们必须要指定数据类型:</p>
<div class="jb51code"><pre class="brush:plain;">fn main() {
let data = r#"
{
"name":"admin",
"age":2,
"work":"play"
}
"#;
let animal: Animal = serde_json::from_str(data).unwrap();
animal.say();
}
</pre></div>
<p>明确指定了<code>animal: Animal</code>,因为没有其他逻辑帮助rust推断出具体的类型是什么。也可以这么写<code>let animal = serde_json::from_str::<Animal>(data).unwrap();</code></p>
<p><strong>注意</strong></p>
<p>需要注意的是:匹配的不同对象结构体的字段不能一致,否则会匹配到枚举的第一个;如果出现包含的情况,我们需要把被包含的结构体放在前面。</p>
<p>比如小猫也有<code>work</code>字段了:</p>
<div class="jb51code"><pre class="brush:plain;">#
struct Cat {
name: String,
age: i32,
work: String,
}
</pre></div>
<p>这是我们再去匹配JSON格式数据,因为数据里有<code>age</code>,我们希望的是匹配小猫<code>Cat</code>,但是它里面完全包含了小狗的字段<code>Dog</code>,而且枚举<code>Animal</code>种小狗在前,所以会直接匹配小狗:</p>
<div class="jb51code"><pre class="brush:plain;">let data = r#"
{
"name":"admin",
"age":2,
"work":"play"
}
"#;
</pre></div>
<p>这样达不到我们想要的结果,所以需要注意调整枚举值的顺序,可以将复杂数据结构放到前面。将<code>Cat</code>放到前面就可以正常工作了。</p>
<div class="jb51code"><pre class="brush:plain;">#
#
enum Animal {
Cat(Cat)
Dog(Dog),
}
</pre></div>
<p class="maodian"></p><h2>动态类型匹配</h2>
<p>上一个方式是我们拿到了具体对象的JSON数据,然后通过序列化,获取到对应的对象实例。如果我们只知道某个类型,需要根据类型初始化具体实例对象。</p>
<p>我们枚举实例对象的类型,定义字符串转枚举类型的方法:</p>
<div class="jb51code"><pre class="brush:plain;">#
#
enum AnimalType {
Dog,
Cat
}
impl AnimalType {
fn str_to_animal_type(str: &str) -> AnimalType {
match str {
"dog" => AnimalType::Dog,
"cat" => AnimalType::Cat,
_ => panic!("unknown type"),
}
}
}
</pre></div>
<p>调用<code>AnimalType</code>获取到枚举类型,然后通过匹配类型来实例化对象,这跟上面的序列化<code>JSON</code>格式后续处理方式一致。</p>
<div class="jb51code"><pre class="brush:plain;">fn main() {
let names = "dog";
match AnimalType::str_to_animal_type(names) {
AnimalType::Dog => {
let dog = Dog {
name: "admin".to_string(),
work: "play".to_string(),
};
dog.say();
}
AnimalType::Cat => {
let cat = Cat {
name: "admin".to_string(),
age: 2,
work: "play".to_string(),
};
cat.say();
}
}
}
</pre></div>
<p class="maodian"></p><h2>Trait 特质</h2>
<p><code>trait</code>是rust中特有的类型,它可以定义对象的行为,然后可以被其他对象实现。实现它的对象可以拥有相同的行为,但是可以拥有不同的内部逻辑。</p>
<p>这可以保证我们在动态获取到不同的对象实例,调用它们的方法时保证方法存在。在创建动态对象时,因为不知掉具体大小,需要使用<code>Box<dyn Trait></code>定义动态对象。</p>
<div class="jb51code"><pre class="brush:plain;">trait AnimalTrait {
fn say(&self);
}
</pre></div>
<p>然后在各个类型中实现<code>AnimalTrait</code>,并实现公共方法<code>say</code>。</p>
<div class="jb51code"><pre class="brush:plain;">impl AnimalTrait for Dog {
fn say(&self) {
println!("{} say wangwang", self.name);
}
}
impl AnimalTrait for Cat {
fn say(&self) {
println!("{} say miamiamia", self.name);
}
}</pre></div>
<p>定义类型都实现<code>AnimalTrait</code>的方法,就可以放心的使用<code>Box<dyn AnimalTrait></code>提供的动态对象了。</p>
<div class="jb51code"><pre class="brush:plain;">impl AnimalType {
fn str_to_animal(str: &str) -> Box<dyn AnimalTrait> {
match str {
"dog" => Box::new(Dog::new("admin".to_string(), "play".to_string())),
"cat" => Box::new(Cat::new("test".to_string())),
_ => panic!("unknown type"),
}
}
}
</pre></div>
<p>方法<code>str_to_animal</code>通过类型匹配获取到对应的实例对象,现在我们不需要再匹配里直接调用方法了。我们拿到动态对象,想调用那个方法就用哪个。</p>
<div class="jb51code"><pre class="brush:plain;">fn main() {
let names = "dog";
let animal = AnimalType::str_to_animal(names);
animal.say();
}
</pre></div>
<p>这样就很方便的进行动态对象的传递,我们不需要关心该调用哪个方法,是否需要导入指定的方法。rust通过<code>Box<dyn AnimalTrait></code>会自动调用合适的实现。</p>
<p class="maodian"></p><h2>From/Into 类型强转</h2>
<p>我们定义了<code>AnimalTrait</code>规范了动态对象的行为,它们在实现了<code>AnimalTrait</code>后,就可以根据动态对象调用它的公共方法了。</p>
<p>但在根据类型创建动态对象时,仍然定义了枚举<code>AnimalType</code>的方法<code>str_to_animal</code>并调用从而匹配到对应的动态对象。</p>
<p>我们还可以使用<code>From</code>trait,通过让<code>AnimalTrait</code>实现<code>From</code>trait,从而直接使用<code>into</code>方法让字符串类型转为动态对象。</p>
<div class="jb51code"><pre class="brush:plain;">impl From<&str> for Box<dyn AnimalTrait> {
fn from(value: &str) -> Self {
match value {
"dog" => Box::new(Dog::new("admin".to_string(), "play".to_string())),
"cat" => Box::new(Cat::new("test".to_string())),
_ => panic!("unknown type"),
}
}
}
</pre></div>
<p>这样的实现可以减少在创建动态对象时的显示函数调用,我们在使用的时候直接调用<code>into()</code>方法即可:</p>
<div class="jb51code"><pre class="brush:plain;">fn main{
let dog: Box<dyn AnimalTrait> = "dog".into();
dog.say();
}
</pre></div>
<p class="maodian"></p><h2>HashMap映射类型</h2>
<p>以上实现方案难免都使用了<code>match</code>进行匹配,而我们在之前说的映射对象的实现,则可以避免<code>match</code>的匹配。</p>
<p>通过<code>HashMap</code>初始化类型映射结构体对象,在使用时通过自定义方法<code>get</code>传入指定的类型,得到动态类型。</p>
<div class="jb51code"><pre class="brush:plain;">struct AnimalFactory {
map: HashMap<String, Box<dyn Fn() -> Box<dyn AnimalTrait>>>,
}
</pre></div>
<p>我们定义了一个结构体<code>AnimalFactory</code>,其中包含一个<code>HashMap</code>类型的字段<code>map</code>,用于存储类型与创建函数的映射关系。</p>
<p>注意到<code>HashMap</code>的值是一个闭包函数而不是直接动态类型,如果直接定义<code>HashMap<String, Box<dyn AnimalTrait>></code>,我们在初始化时就必须实例化创建对象实例,这就导致具体对象的实例只有一个而避免不了处理所有权的问题。如果我们需要传递所有权,就必须使用<code>Arc</code>了。</p>
<p>定义了工厂结构体<code>AnimalFactory</code>,定义初始化函数<code>new</code>:</p>
<div class="jb51code"><pre class="brush:plain;">impl AnimalFactory {
fn new() -> Self {
map.insert(
"dog".to_string(),
Box::new(|| {
Box::new(Dog::new("admin".to_string(), "play".to_string())) as Box<dyn AnimalTrait>
}) as Box<dyn Fn() -> Box<dyn AnimalTrait>>,
);
map.insert(
"cat".to_string(),
Box::new(|| Box::new(Cat::new("test".to_string()))),
);
AnimalFactory { map }
}
}
</pre></div>
<p>由于<code>HashMap</code>需要定义具体的类型,我们在插入类型<code>Dog</code>时无法匹配定义的<code>Box<dyn Fn() -> Box<dyn AnimalTrait>></code>导致报错,这就需要我们手动强转类型。</p>
<p>为了简化类型书写,我们定义一个类型替代:</p>
<div class="jb51code"><pre class="brush:plain;">type AnimalDynType = Box<dyn Fn() -> Box<dyn AnimalTrait>>;
</pre></div>
<p>我们已经初始化了映射表,定义根据具体类型获取动态对象的方法:</p>
<div class="jb51code"><pre class="brush:plain;">impl AnimalFactory {
fn get(&self, name: &str) -> Box<dyn AnimalTrait> {
match self.map.get(name) {
Some(create_fn) => create_fn(),
None => panic!("not found"),
}
}
}
</pre></div>
<p>在使用时,首先创建一个AnimalFactory对象,然后调用get方法,传入具体的类型名称,即可获取对应的动态对象。</p>
<div class="jb51code"><pre class="brush:plain;">fn main() {
let animal = AnimalFactory::new();
let dog = animal.get_animal("dog");
dog.say();
}
</pre></div>
<p class="maodian"></p><h2>最后</h2>
<p>这几种实现方式都有一定的使用场景,根据实际需求选择合适的方式。</p>
<p>到此这篇关于Rust中实例化动态对象的示例详解的文章就介绍到这了,更多相关Rust实例化动态对象内容请搜索琼殿技术社区以前的文章或继续浏览下面的相关文章希望大家以后多多支持琼殿技术社区!</p>
<div class="art_xg">
<b>您可能感兴趣的文章:</b><ul><li>Rust中的引用循环与内存泄漏详解</li><li>jupyter安装失败的解决,问题出在rust环境和32位python</li><li>Rust 中的闭包之捕获环境的匿名函数</li><li>Rust中的内部可变性与RefCell<T>详解</li></ul>
</div>
</div>
<!--endmain-->
頁:
[1]