多实现与键控服务
问题场景
一个 trait 有多个实现,需要在运行时根据条件选择: - 支付网关:微信支付、支付宝、信用卡 - 日志输出:文件日志、控制台日志、远程日志 - 数据存储:PostgreSQL、MySQL、SQLite
rust-dix 解决方案:keyed 服务
注册多个实现
use rust_dix::*;
use std::sync::Arc;
// 接口
trait IPaymentGateway: Send + Sync {
fn pay(&self, amount: f64) -> Result<String, String>;
fn name(&self) -> &'static str;
}
// 实现一:微信支付
struct WechatPay;
impl IPaymentGateway for WechatPay {
fn pay(&self, amount: f64) -> Result<String, String> {
Ok(format!("微信支付:¥{}", amount))
}
fn name(&self) -> &'static str { "wechat" }
}
// 实现二:支付宝
struct Alipay;
impl IPaymentGateway for Alipay {
fn pay(&self, amount: f64) -> Result<String, String> {
Ok(format!("支付宝:¥{}", amount))
}
fn name(&self) -> &'static str { "alipay" }
}
// 注册:默认 + keyed
let provider = ServiceCollection::new()
// 默认策略(singleton 注册,无 key)
.singleton::<dyn IPaymentGateway>(|_| Arc::new(WechatPay))
// 替代策略(keyed 注册)
.keyed_singleton::<dyn IPaymentGateway>("wechat", |_| Arc::new(WechatPay))
.keyed_singleton::<dyn IPaymentGateway>("alipay", |_| Arc::new(Alipay))
.keyed_singleton::<dyn IPaymentGateway>("credit", |_| Arc::new(CreditCardPay))
.build()
.unwrap();
解析:按 key 选择实现
// 默认解析
let default: Arc<dyn IPaymentGateway> = provider.get().unwrap();
assert_eq!(default.name(), "wechat");
// 按名称选择
let alipay: Arc<dyn IPaymentGateway> = provider.get_keyed("alipay").unwrap();
assert_eq!(alipay.name(), "alipay");
// 安全解析(可选)
if let Some(gateway) = provider.try_get_keyed::<dyn IPaymentGateway>("credit") {
gateway.pay(100.0);
}
批量获取:get_all
// 获取所有实现
let all: Vec<Arc<dyn IPaymentGateway>> = provider.get_all();
assert_eq!(all.len(), 3); // wechat + alipay + credit
// 遍历所有实现(如检查所有网关可用性)
for gateway in &all {
println!("可用网关: {}", gateway.name());
}
多态实现模式
模式 A:默认 + keyed(推荐)
ServiceCollection::new()
.singleton::<dyn Trait>(|_| Arc::new(DefaultImpl)) // 默认
.keyed_singleton::<dyn Trait>("alt1", |_| Arc::new(AltImpl1)) // 替代
.keyed_singleton::<dyn Trait>("alt2", |_| Arc::new(AltImpl2))
.build()
- `provider.get()` 返回默认实现 - `provider.get_keyed("alt1")` 返回指定实现
模式 B:全 keyed(无默认)
ServiceCollection::new()
.keyed_singleton::<dyn Trait>("impl1", |_| Arc::new(Impl1))
.keyed_singleton::<dyn Trait>("impl2", |_| Arc::new(Impl2))
.keyed_singleton::<dyn Trait>("impl3", |_| Arc::new(Impl3))
.build()
- 无 `provider.get()`,必须用 `get_keyed` - 适合强制指定策略的场景
模式 C:运行时路由(策略工厂)
struct PaymentService {
provider: Arc<ServiceProvider>,
default_gateway: &'static str,
}
impl PaymentService {
fn pay(&self, method: &str, amount: f64) -> Result<String, String> {
// 尝试按指定方法获取,失败则回退默认
let gateway: Arc<dyn IPaymentGateway> =
self.provider.try_get_keyed(method)
.unwrap_or_else(|| self.provider.get().unwrap());
gateway.pay(amount)
}
}
实际应用示例
场景:多日志输出
trait ILogOutput: Send + Sync {
fn write(&self, level: LogLevel, msg: &str);
}
struct FileOutput { path: String }
struct ConsoleOutput;
struct RemoteOutput { endpoint: String }
let provider = ServiceCollection::new()
.keyed::<dyn ILogOutput>("file", |_| Arc::new(FileOutput { path: "/var/log/app.log" }))
.keyed::<dyn ILogOutput>("console", |_| Arc::new(ConsoleOutput))
.keyed::<dyn ILogOutput>("remote", |_| Arc::new(RemoteOutput { endpoint: "log-server" }))
.build()
.unwrap();
// 组合多个输出
let outputs: Vec<Arc<dyn ILogOutput>> = provider.get_all();
for output in &outputs {
output.write(LogLevel::Info, "服务启动");
}
场景:多租户存储后端
trait IStorage: Send + Sync {
fn save(&self, tenant: &str, data: &[u8]) -> Result<(), String>;
}
let provider = ServiceCollection::new()
.keyed::<dyn IStorage>("tenant_a", |_| Arc::new(PostgresStorage))
.keyed::<dyn IStorage>("tenant_b", |_| Arc::new(MysqlStorage))
.build()
.unwrap();
// 按租户选择存储
let storage: Arc<dyn IStorage> = provider.get_keyed(tenant_id).unwrap();
storage.save(tenant_id, data);
key 与生命周期的组合
| 方法 | 生命周期 | 用途 | |-----|---------|------| | `keyed_singleton::<T>(k, f)` | Singleton | 全局共享的策略 | | `keyed_transient::<T>(k, f)` | Transient | 每次创建的策略 | | `keyed_scoped::<T>(k, f)` | Scoped | Scope 内共享的策略 |
// 策略实例每次新建
.keyed_transient::<dyn IAlgorithm>("sort", |_| Arc::new(QuickSort))
// 策略实例 Scope 内共享
.keyed_scoped::<dyn ICache>("session", |_| Arc::new(SessionCache))
最佳实践
| ✅ 推荐 | ❌ 避免 | |--------|--------| | 默认 + keyed 组合 | 只 keyed 无默认导致 `get()` 返回 `Err(ServiceNotFound)` | | key 名称语义明确(如 "wechat") | key 名称模糊(如 "impl1") | | 用 get_all 批量处理观察者 | 手动逐个 get_keyed | | 策略选择逻辑集中封装 | 分散在各处的 if/else |
下一步
下一节:[第五章 设计模式实践](05-design-patterns/INDEX.md) — 看策略模式如何与 DI 容器配合。