具有多个结构的泛型和依赖倒置
Generics and dependency inversion with multiple structs
我正在尝试在 Rust 中创建一个干净的架构结构,其中一些结构使用特性进行依赖倒置。
我的想法基本上是有:
- 只有一个字段的
User
模型。
- 符合存储库 trait/interface 的存储库,其中包含从 MySQL 数据库检索用户的方法。
- 依赖于存储库 trait/interface 的用例,并在使用
new
方法实例化时接收此存储库的实例。我还拥有一个 execute
方法来触发存储库操作。
- 依赖于用例 trait/interface 的控制器,并在使用
new
方法实例化时接收此用例的实例。它还包含一个 execute
方法来触发用例操作。
User:
+ id
UserRepository complies with IUserRepository:
- get_all_users: () -> Vec<Users>
GetUsersUseCase complies with IGetUsersUseCase:
+ user_repository: IUserRepository
- new: (user_repository: IUserRepository) -> GetUsersUseCase
- execute: () -> Vec<Users>
GetUsersController:
+ get_users_use_case: IGetUsersUseCase
- new: (user_use_case: IGetUsersUseCase) -> GetUsersController
- execute: () -> Vec<Users>
我有这个的实现,但我对泛型有疑问。基本代码:
代码:
// MODEL
#[derive(Debug)]
struct User {
id: i8,
}
// REPOSITORY for DB
trait IUserRepository {
fn get_users(&self) -> Vec<User>;
}
struct MySQLUserRepository;
impl IUserRepository for MySQLUserRepository {
fn get_users(&self) -> Vec<User> {
let mock_user1 = User { id: 1 };
let mock_user2 = User { id: 2 };
let mock_users = vec![mock_user1, mock_user2];
mock_users
}
}
// USE CASE
trait IGetUsersUseCase {
fn new<T: IUserRepository>(repository: T) -> GetUsersUseCase<T>;
fn execute(&self) -> Vec<User>;
}
struct GetUsersUseCase<T> {
user_repo: T,
}
impl<T: IUserRepository> IGetUsersUseCase for GetUsersUseCase<T> {
fn new<K: IUserRepository>(user_repo: K) -> GetUsersUseCase<K> {
GetUsersUseCase { user_repo }
}
fn execute(&self) -> Vec<User> {
let users = self.user_repo.get_users();
users
}
}
// CONTROLLER for HTTP requests
struct GetUsersController<T> {
get_users_use_case: T,
}
impl<T: IGetUsersUseCase> GetUsersController<T> {
fn new(get_users_use_case: T) -> GetUsersController<T> {
GetUsersController { get_users_use_case }
}
fn execute(&self) -> Vec<User> {
let users = self.get_users_use_case.execute();
users
}
}
fn main() {
// Lets imagine we are handling an HTTP request
let mysql_repo = MySQLUserRepository {};
// Error here: cannot infer type for type parameter `T` declared on the struct `GetUsersUseCase`
let get_users_use_case = GetUsersUseCase::new(mysql_repo);
let get_users_controller = GetUsersController::new(get_users_use_case);
let users = get_users_controller.execute();
println!("{:?}", users);
}
如您所见,问题出在 GetUsersUseCase 的实现上 —impl<T: IUserRepository> IGetUsersUseCase for GetUsersUseCase<T>
—。由于实现接收到两个通用参数,因此在构造 GetUsersUseCase::new(mysql_repo)
时收到以下错误:
cannot infer type for type parameter `T` declared on the struct `GetUsersUseCase`rustc(E0282)
一种解决方法是在 GetUsersUseCase
public 处创建 user_repo
,而不是像往常一样使用 GetUsersUseCase::new(mysql_repo)
实例化它:
[…]
struct GetUsersUseCase<T> {
pub user_repo: T,
}
[…]
let get_users_use_case = GetUsersUseCase {
user_repo: mysql_repo,
};
[…]
这行得通,但我真的很想知道如何使用 public 函数构造结构而不暴露私有字段。
这里是制作这个场地的游乐场public:https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=917cee9d969dccd08c4e27753d04994f
欢迎提出任何想法!
快速修复您的代码:
// No more error here
let get_users_use_case = GetUsersUseCase::<MySQLUserRepository>::new(mysql_repo);
为什么会这样?
它从 trait IGetUsersUseCase
开始:它不需要泛型类型,但是,它的方法 fn new<T>()
需要,因此 struct GetUsersUseCase<T>
。看看这里:
impl<T: IUserRepository> IGetUsersUseCase for GetUsersUseCase<T> {
fn new<K: IUserRepository>(user_repo: K) -> GetUsersUseCase<K> {
GetUsersUseCase { user_repo }
}
}
这是一个正确的实现 特征,它实际上表明 T
没有被引用 K
,就像它们是 “不同的”(K
被定义为函数,并且 T
是为结构定义的,两者都没有为特征定义)。因此,需要用turbofish算子帮助编译器(GetUsersUseCase::<MySQLUserRepository>
).
这不是唯一的方法。例如,您可以将泛型从方法级别移动到特征级别。
// BEFORE
trait IGetUsersUseCase {
fn new<T: IUserRepository>(repository: T) -> GetUsersUseCase<T>;
fn execute(&self) -> Vec<User>;
}
// AFTER
trait IGetUsersUseCase<T: IUserRepository> {
fn new(repository: T) -> GetUsersUseCase<T>;
fn execute(&self) -> Vec<User>;
}
但是,您会发现在下一次特征声明时事情变得相当复杂。 (见游乐场https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=38f41d8b50464acc28c7dc0c70842e6e)。
我会推荐您审查您的“接口”(特征)设计。现在,你有了一种“继承”Repo -> UseCase -> Controller
,但是,例如,你可以将控制器实现为一组简单的函数,也许你不需要到处都需要泛型。
我正在尝试在 Rust 中创建一个干净的架构结构,其中一些结构使用特性进行依赖倒置。
我的想法基本上是有:
- 只有一个字段的
User
模型。 - 符合存储库 trait/interface 的存储库,其中包含从 MySQL 数据库检索用户的方法。
- 依赖于存储库 trait/interface 的用例,并在使用
new
方法实例化时接收此存储库的实例。我还拥有一个execute
方法来触发存储库操作。 - 依赖于用例 trait/interface 的控制器,并在使用
new
方法实例化时接收此用例的实例。它还包含一个execute
方法来触发用例操作。
User:
+ id
UserRepository complies with IUserRepository:
- get_all_users: () -> Vec<Users>
GetUsersUseCase complies with IGetUsersUseCase:
+ user_repository: IUserRepository
- new: (user_repository: IUserRepository) -> GetUsersUseCase
- execute: () -> Vec<Users>
GetUsersController:
+ get_users_use_case: IGetUsersUseCase
- new: (user_use_case: IGetUsersUseCase) -> GetUsersController
- execute: () -> Vec<Users>
我有这个的实现,但我对泛型有疑问。基本代码:
代码:
// MODEL
#[derive(Debug)]
struct User {
id: i8,
}
// REPOSITORY for DB
trait IUserRepository {
fn get_users(&self) -> Vec<User>;
}
struct MySQLUserRepository;
impl IUserRepository for MySQLUserRepository {
fn get_users(&self) -> Vec<User> {
let mock_user1 = User { id: 1 };
let mock_user2 = User { id: 2 };
let mock_users = vec![mock_user1, mock_user2];
mock_users
}
}
// USE CASE
trait IGetUsersUseCase {
fn new<T: IUserRepository>(repository: T) -> GetUsersUseCase<T>;
fn execute(&self) -> Vec<User>;
}
struct GetUsersUseCase<T> {
user_repo: T,
}
impl<T: IUserRepository> IGetUsersUseCase for GetUsersUseCase<T> {
fn new<K: IUserRepository>(user_repo: K) -> GetUsersUseCase<K> {
GetUsersUseCase { user_repo }
}
fn execute(&self) -> Vec<User> {
let users = self.user_repo.get_users();
users
}
}
// CONTROLLER for HTTP requests
struct GetUsersController<T> {
get_users_use_case: T,
}
impl<T: IGetUsersUseCase> GetUsersController<T> {
fn new(get_users_use_case: T) -> GetUsersController<T> {
GetUsersController { get_users_use_case }
}
fn execute(&self) -> Vec<User> {
let users = self.get_users_use_case.execute();
users
}
}
fn main() {
// Lets imagine we are handling an HTTP request
let mysql_repo = MySQLUserRepository {};
// Error here: cannot infer type for type parameter `T` declared on the struct `GetUsersUseCase`
let get_users_use_case = GetUsersUseCase::new(mysql_repo);
let get_users_controller = GetUsersController::new(get_users_use_case);
let users = get_users_controller.execute();
println!("{:?}", users);
}
如您所见,问题出在 GetUsersUseCase 的实现上 —impl<T: IUserRepository> IGetUsersUseCase for GetUsersUseCase<T>
—。由于实现接收到两个通用参数,因此在构造 GetUsersUseCase::new(mysql_repo)
时收到以下错误:
cannot infer type for type parameter `T` declared on the struct `GetUsersUseCase`rustc(E0282)
一种解决方法是在 GetUsersUseCase
public 处创建 user_repo
,而不是像往常一样使用 GetUsersUseCase::new(mysql_repo)
实例化它:
[…]
struct GetUsersUseCase<T> {
pub user_repo: T,
}
[…]
let get_users_use_case = GetUsersUseCase {
user_repo: mysql_repo,
};
[…]
这行得通,但我真的很想知道如何使用 public 函数构造结构而不暴露私有字段。
这里是制作这个场地的游乐场public:https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=917cee9d969dccd08c4e27753d04994f
欢迎提出任何想法!
快速修复您的代码:
// No more error here
let get_users_use_case = GetUsersUseCase::<MySQLUserRepository>::new(mysql_repo);
为什么会这样?
它从 trait IGetUsersUseCase
开始:它不需要泛型类型,但是,它的方法 fn new<T>()
需要,因此 struct GetUsersUseCase<T>
。看看这里:
impl<T: IUserRepository> IGetUsersUseCase for GetUsersUseCase<T> {
fn new<K: IUserRepository>(user_repo: K) -> GetUsersUseCase<K> {
GetUsersUseCase { user_repo }
}
}
这是一个正确的实现 特征,它实际上表明 T
没有被引用 K
,就像它们是 “不同的”(K
被定义为函数,并且 T
是为结构定义的,两者都没有为特征定义)。因此,需要用turbofish算子帮助编译器(GetUsersUseCase::<MySQLUserRepository>
).
这不是唯一的方法。例如,您可以将泛型从方法级别移动到特征级别。
// BEFORE
trait IGetUsersUseCase {
fn new<T: IUserRepository>(repository: T) -> GetUsersUseCase<T>;
fn execute(&self) -> Vec<User>;
}
// AFTER
trait IGetUsersUseCase<T: IUserRepository> {
fn new(repository: T) -> GetUsersUseCase<T>;
fn execute(&self) -> Vec<User>;
}
但是,您会发现在下一次特征声明时事情变得相当复杂。 (见游乐场https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=38f41d8b50464acc28c7dc0c70842e6e)。
我会推荐您审查您的“接口”(特征)设计。现在,你有了一种“继承”Repo -> UseCase -> Controller
,但是,例如,你可以将控制器实现为一组简单的函数,也许你不需要到处都需要泛型。