具有多个结构的泛型和依赖倒置

Generics and dependency inversion with multiple structs

我正在尝试在 Rust 中创建一个干净的架构结构,其中一些结构使用特性进行依赖倒置。

我的想法基本上是有:


  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>

我有这个的实现,但我对泛型有疑问。基本代码:

游乐场:https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=ca1f4f9d6cfe4df2864d7e574966ad6b.

代码:

// 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,但是,例如,你可以将控制器实现为一组简单的函数,也许你不需要到处都需要泛型。