“不同结构体的集合,以特性为键”

huangapple go评论67阅读模式
英文:

Collections of different structs keyed by trait

问题

I understand your request. Here is the translated content:

作为我学习Rust的过程的一部分,我试图转换我已经使用过多次的一种模式。这是一个“服务”仓库,这些服务是由一个键(通常是接口名称)注册的。然后可以查询该仓库以检索“服务”,然后可以使用它。我以前曾将其用作简单的DI方法,允许在测试期间用模拟对象替换实现。

我在尝试在Rust中实现这个简单版本时遇到了一些问题。

我希望看到的是这种类型的代码:

// 服务合同
trait DoSomethingService {
    fn do_something(&self);
}

// 服务实现
struct DoSomethingServiceImpl {}

impl DoSomethingService for DoSomethingServiceImpl {
    fn do_something(&self) {
        println!("为MyService做点什么");
    }
}

// BaseService是一个标记trait
impl BaseService for DoSomethingServiceImpl {}

fn main() {
    let mut repo = ServiceRepo::new();

    let service1 = DoSomethingServiceImpl {};

    // 将我们的实现注册到仓库中作为DoSomethingService trait
    repo.register_service::<DoSomethingService>(Box::new(service1));

    // 从仓库中获取服务,并将其强制转换为trait,而不是具体类型
    if let Some(x) = repo.get_service::<DoSomethingService>() {
        x.do_something();
    }
}

我的当前实现看起来像这样:

#[derive(Default)]
struct ServiceRepo {
    services: HashMap<String, Box<dyn BaseService>>,
}

impl ServiceRepo {
    fn new() -> Self {
        Self {
            ..Default::default()
        }
    }

    fn register_service<T: BaseService>(&mut self, service: Box<dyn BaseService>) {
        self.services
            .insert(std::any::type_name::<T>().to_string(), service);
    }

    fn get_service<T: BaseService>(&self) -> Option<&Box<T>> {
        let service = self.services.get(&std::any::type_name::<T>().to_string());

        // TODO: 在这里返回Option<&T>
        // 不确定该做什么!
    }
}

我“认为”我已经很好地存储了服务实现,但是在预先将其作为所需trait强制转换时遇到了问题。

所以有几个问题:

  1. 这是Rust中合理的模式吗?如果不是,为什么?
  2. 我该如何实现这个?我目前漏掉了什么?
英文:

As part of my Rust learning process, I'm trying to convert a pattern I've used several times. This is a repository of "services" that are registered by a key (usually an interface name). This repository is then able to be queried to retrieve a "service" which can then be used. I've used this in the past as a simple DI method to allow the implementations to be replaced with mocks during tests.

I'm having a bit of trouble implementing a simple version of this in Rust.

What I would like to see is this sort of code:

// The service contract
trait DoSomethingService {
    fn do_something(&amp;self);
}

// The service implementation
struct DoSomethingServiceImpl {}

impl DoSomethingService for DoSomethingServiceImpl {
    fn do_something(&amp;self) {
        println!(&quot;Doing something for MyService&quot;);
    }
}

// BaseService is a marker trait
impl BaseService for DoSomethingServiceImpl {}


fn main() {
    let mut repo = ServiceRepo::new();

    let service1 = DoSomethingServiceImpl {};

    // Register our implementation with the repo as the trait DoSomethingService
    repo.register_service::&lt;DoSomethingService&gt;(Box::new(service1));

    // Get the service from the repo and have it cast to the trait NOT the concrete type
    if let Some(x) = repo.get_service::&lt;DoSomethingService&gt;() {
        x.do_something();
    }
}

My current implementation looks like this

#[derive(Default)]
struct ServiceRepo {
    services: HashMap&lt;String, Box&lt;dyn BaseService&gt;&gt;,
}

impl ServiceRepo {
    fn new() -&gt; Self {
        Self {
            ..Default::default()
        }
    }

    fn register_service&lt;T: BaseService&gt;(&amp;mut self, service: Box&lt;dyn BaseService&gt;) {
        self.services
            .insert(std::any::type_name::&lt;T&gt;().to_string(), service);
    }

    fn get_service&lt;T: BaseService&gt;(&amp;self) -&gt; Option&lt;&amp;Box&lt;T&gt;&gt; {
        let service = self.services.get(&amp;std::any::type_name::&lt;T&gt;().to_string());

        // TODO: Want to return an Option&lt;&amp;T&gt; here
        // Not sure what to do!
    }
}

I "think" I'm storing the service implementation fine but retrieving it pre-cast as the required trait is evading me.

So a couple of questions:

  1. Is this a reasonable pattern in Rust? If not why not?
  2. How would I implement this? What am I currently missing?

答案1

得分: 0

你不能按照你描述的方式来做这个。原因是这没有任何意义。特质 "describe" 类型(它们的能力),而不是反过来。然而,你已经很接近了,你可以反转你的解决方案,这样就可以让系统工作。

你必须将 types(而不是 traits)视为你的服务,你可以使用 dyn Any 来存储它们。例如:

use std::any::Any;

struct Service1;

impl Service1 {
    fn do_thing(&self) {
        println!("Service1")
    }
}

struct Service2;

impl Service2 {
    fn do_other_thing(&mut self) {
        println!("Service2")
    }
}

struct Services {
    services: Vec<Box<dyn Any>>,
}

impl Services {
    fn new() -> Self {
        Self { services: vec![] }
    }

    fn register(&mut self, service: Box<dyn Any>) {
        self.services.push(service)
    }

    fn get_service<S: 'static>(&self) -> Option<&S> {
        self.services
            .iter()
            .map(|s| s.downcast_ref::<S>())
            .find(Option::is_some)
            .flatten()
    }

    fn get_service_mut<S: 'static>(&mut self) -> Option<&mut S> {
        self.services
            .iter_mut()
            .map(|s| s.downcast_mut::<S>())
            .find(Option::is_some)
            .flatten()
    }
}

fn main() {
    let mut services = Services::new();
    services.register(Box::new(Service1));

    services.get_service::<Service1>().unwrap().do_thing();
    assert!(services.get_service::<Service2>().is_none());

    services.register(Box::new(Service2));

    services.get_service::<Service1>().unwrap().do_thing();
    services
        .get_service_mut::<Service2>()
        .unwrap()
        .do_other_thing();
}

编辑:顺便说一句,不要使用 std::any::type_name 的输出作为唯一标识服务的基础。这仅用于调试目的!使用 std::any::TypeId 来唯一标识服务,可以使用 std::any::TypeId::of 来获取。

英文:

You cannot do this the way how you described this. And the reason is that it doesn't make any sense. Traits "describe" types (their capabilities), not the other way around. However you are close, and you could reverse your solution and have system that would work.

You must think of types (not traits) as your services, and you can store that using dyn Any. For example:

use std::any::Any;

struct Service1;

impl Service1 {
    fn do_thing(&amp;self) {
        println!(&quot;Service1&quot;)
    }
}

struct Service2;

impl Service2 {
    fn do_other_thing(&amp;mut self) {
        println!(&quot;Service2&quot;)
    }
}

struct Services {
    services: Vec&lt;Box&lt;dyn Any&gt;&gt;,
}

impl Services {
    fn new() -&gt; Self {
        Self { services: vec![] }
    }

    fn register(&amp;mut self, service: Box&lt;dyn Any&gt;) {
        self.services.push(service)
    }

    fn get_service&lt;S: &#39;static&gt;(&amp;self) -&gt; Option&lt;&amp;S&gt; {
        self.services
            .iter()
            .map(|s| s.downcast_ref::&lt;S&gt;())
            .find(Option::is_some)
            .flatten()
    }

    fn get_service_mut&lt;S: &#39;static&gt;(&amp;mut self) -&gt; Option&lt;&amp;mut S&gt; {
        self.services
            .iter_mut()
            .map(|s| s.downcast_mut::&lt;S&gt;())
            .find(Option::is_some)
            .flatten()
    }
}

fn main() {
    let mut services = Services::new();
    services.register(Box::new(Service1));

    services.get_service::&lt;Service1&gt;().unwrap().do_thing();
    assert!(services.get_service::&lt;Service2&gt;().is_none());

    services.register(Box::new(Service2));

    services.get_service::&lt;Service1&gt;().unwrap().do_thing();
    services
        .get_service_mut::&lt;Service2&gt;()
        .unwrap()
        .do_other_thing();
}

EDIT. And by the way do not use output of std::any::type_name as basis for uniquely identifying services. This is only intended for debugging purposes! Use std::any::TypeId obtained with std::any::TypeId::of.

huangapple
  • 本文由 发表于 2023年5月23日 00:12:14
  • 转载请务必保留本文链接:https://go.coder-hub.com/76308098.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定