You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

在Rust中结合Clean Architecture实现MVP模式的最优方案咨询

在Rust的Clean Architecture中实现MVP模式的最优方案

你的代码目前存在两个核心问题:

  1. 循环依赖与所有权冲突:LoginPresenter持有&mut dyn ILoginView,而LoginView又持有LoginPresenter,这种双向直接引用在Rust的所有权系统下会触发生命周期错误,且无法编译。
  2. 交互逻辑缺失:现有trait仅定义了new方法,没有提供Presenter更新View状态、View触发Presenter业务逻辑的接口。

以下是两种符合Rust特性与Clean Architecture规范的实现方案,适配不同场景需求:


方案一:基于Arc的共享可变引用(同步/简单异步场景)

该方案通过Arc<Mutex>实现Presenter与View的安全共享,严格遵循Clean Architecture的依赖规则(内层定义抽象,外层依赖抽象)。

1. 定义View抽象(接口层)

在Clean Architecture的接口层定义View的行为抽象,让Presenter依赖该抽象而非具体View:

// 接口层:定义View需要实现的UI更新方法
pub trait ILoginView {
    fn show_login_success(&mut self, user_info: String);
    fn show_login_error(&mut self, error_msg: String);
}

2. 实现UseCase层(业务逻辑)

模拟登录业务逻辑,完全独立于View和Presenter:

// UseCase层:处理核心业务逻辑
pub struct LoginUseCase;

impl LoginUseCase {
    pub async fn login(&self, username: String, password: String) -> Result<String, Box<dyn std::error::Error>> {
        // 模拟真实登录逻辑(如调用API、数据库查询)
        if username == "admin" && password == "123456" {
            Ok("用户:admin".to_string())
        } else {
            Err("用户名或密码错误".into())
        }
    }
}

3. 实现Presenter(Use Case层)

Presenter持有View抽象的共享可变引用,以及UseCase实例,负责协调业务逻辑与UI更新:

use std::sync::{Arc, Mutex};

// Presenter层:协调UseCase与View
pub struct LoginPresenter {
    view: Arc<Mutex<dyn ILoginView>>,
    login_use_case: LoginUseCase,
}

impl LoginPresenter {
    pub fn new(view: Arc<Mutex<dyn ILoginView>>, login_use_case: LoginUseCase) -> Self {
        Self { view, login_use_case }
    }

    // 暴露给View的登录触发方法
    pub async fn handle_login(&self, username: String, password: String) {
        match self.login_use_case.login(username, password).await {
            Ok(user_info) => {
                // 更新View状态
                self.view.lock().unwrap().show_login_success(user_info);
            }
            Err(e) => {
                self.view.lock().unwrap().show_login_error(e.to_string());
            }
        }
    }
}

4. 实现具体View(外层UI层)

View持有Presenter的共享引用,实现ILoginView抽象,并触发业务逻辑:

use std::sync::Arc;

// UI层:具体View实现
pub struct LoginView {
    presenter: Arc<LoginPresenter>,
    // 模拟View的状态(如UI文本、按钮状态)
    current_status: String,
}

impl LoginView {
    pub fn new(presenter: Arc<LoginPresenter>) -> Self {
        Self {
            presenter,
            current_status: "准备就绪".to_string(),
        }
    }

    // 模拟用户点击登录按钮的操作
    pub async fn on_login_clicked(&mut self, username: String, password: String) {
        self.presenter.handle_login(username, password).await;
    }
}

// 实现View抽象,让Presenter可以更新UI
impl ILoginView for LoginView {
    fn show_login_success(&mut self, user_info: String) {
        self.current_status = format!("登录成功:{}", user_info);
        println!("{}", self.current_status); // 模拟UI渲染
    }

    fn show_login_error(&mut self, error_msg: String) {
        self.current_status = format!("登录失败:{}", error_msg);
        println!("{}", self.current_status); // 模拟UI渲染
    }
}

5. 初始化与运行(解决循环依赖)

通过先创建占位符再填充的方式,避免循环引用的编译错误:

use std::sync::{Arc, Mutex};

#[tokio::main]
async fn main() {
    // 1. 创建UseCase实例
    let login_use_case = LoginUseCase;

    // 2. 创建View的共享占位符(先空初始化,后续填充)
    let view_placeholder = Arc::new(Mutex::new(None::<LoginView>));

    // 3. 创建Presenter,传入View占位符的克隆
    let presenter = Arc::new(LoginPresenter::new(
        Arc::clone(&view_placeholder) as Arc<Mutex<dyn ILoginView>>,
        login_use_case,
    ));

    // 4. 填充View到占位符中
    let mut view_lock = view_placeholder.lock().unwrap();
    *view_lock = Some(LoginView::new(Arc::clone(&presenter)));

    // 5. 模拟用户操作
    let mut view = view_lock.as_mut().unwrap();
    view.on_login_clicked("admin".to_string(), "123456".to_string()).await;
    view.on_login_clicked("guest".to_string(), "wrong_pass".to_string()).await;
}

方案二:基于消息通道的异步解耦(高并发/复杂异步场景)

该方案用Tokio的mpsc消息通道替代Mutex,实现Presenter与View的无锁异步通信,更符合Rust的并发模型。

1. 定义消息类型

// 定义Presenter发送给View的消息
pub enum LoginViewMsg {
    Success(String),
    Error(String),
}

2. 改造Presenter

Presenter持有消息发送器,无需直接引用View:

use tokio::sync::mpsc;

pub struct LoginPresenter {
    tx: mpsc::Sender<LoginViewMsg>,
    login_use_case: LoginUseCase,
}

impl LoginPresenter {
    pub fn new(tx: mpsc::Sender<LoginViewMsg>, login_use_case: LoginUseCase) -> Self {
        Self { tx, login_use_case }
    }

    pub async fn handle_login(&self, username: String, password: String) {
        match self.login_use_case.login(username, password).await {
            Ok(user_info) => {
                self.tx.send(LoginViewMsg::Success(user_info)).await.unwrap();
            }
            Err(e) => {
                self.tx.send(LoginViewMsg::Error(e.to_string())).await.unwrap();
            }
        }
    }
}

3. 改造View

View持有消息接收器,监听并处理Presenter发送的消息:

use std::sync::Arc;

pub struct LoginView {
    presenter: Arc<LoginPresenter>,
    rx: mpsc::Receiver<LoginViewMsg>,
    current_status: String,
}

impl LoginView {
    pub fn new(presenter: Arc<LoginPresenter>, rx: mpsc::Receiver<LoginViewMsg>) -> Self {
        Self {
            presenter,
            rx,
            current_status: "准备就绪".to_string(),
        }
    }

    // 启动消息监听循环
    pub async fn run(&mut self) {
        while let Some(msg) = self.rx.recv().await {
            match msg {
                LoginViewMsg::Success(user_info) => {
                    self.current_status = format!("登录成功:{}", user_info);
                    println!("{}", self.current_status);
                }
                LoginViewMsg::Error(error_msg) => {
                    self.current_status = format!("登录失败:{}", error_msg);
                    println!("{}", self.current_status);
                }
            }
        }
    }

    pub async fn on_login_clicked(&self, username: String, password: String) {
        self.presenter.handle_login(username, password).await;
    }
}

4. 初始化与运行

#[tokio::main]
async fn main() {
    let login_use_case = LoginUseCase;
    // 创建消息通道(缓冲区大小10)
    let (tx, rx) = mpsc::channel(10);

    let presenter = Arc::new(LoginPresenter::new(tx, login_use_case));
    let mut view = LoginView::new(Arc::clone(&presenter), rx);

    // 启动View的消息监听任务
    tokio::spawn(async move {
        view.run().await;
    });

    // 模拟用户操作
    presenter.handle_login("admin".to_string(), "123456".to_string()).await;
    presenter.handle_login("guest".to_string(), "wrong_pass".to_string()).await;

    // 等待消息处理完成
    tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
}

方案对比与选择

方案优点适用场景
Arc共享引用逻辑直观,同步场景下代码简洁简单UI、低并发同步业务
消息通道异步解耦无锁设计,并发安全,解耦程度更高高并发异步业务、复杂UI交互

核心设计原则

  1. 符合Clean Architecture:Presenter依赖View抽象(定义在内层),View依赖Presenter实例(外层依赖内层),UseCase完全独立于UI层。
  2. 遵循Rust所有权规则:用Arc实现共享引用,Mutex或消息通道处理可变状态,避免循环引用与生命周期错误。
  3. 清晰的交互流程:View触发操作 → Presenter调用UseCase → UseCase返回结果 → Presenter通知View更新。

内容的提问来源于stack exchange,提问作者FlensT

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.24 21:42:21