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

C# WinForms MVP-被动视图模式下Model的处理疑问

嘿,刚上手MVP-PV模式时,Model的定位和实例化确实是很容易纠结的点,结合你用原生CRUD(不用EF)的场景,我给你捋清楚这两个问题:

一、Model该包含CRUD逻辑还是仅存属性?

我建议你把CRUD逻辑从Model里抽离出来,让Model只做纯数据属性的载体——也就是常说的「数据传输对象(DTO)」或者「领域实体(无业务逻辑版)」。原因很简单:这符合单一职责原则,让每个类只干一件事。

举个具体的例子:

纯属性的Model

public class User
{
    public int Id { get; set; }
    public string Username { get; set; }
    public string Phone { get; set; }
}

单独的CRUD处理类(比如叫Repository)

public class UserRepository
{
    private readonly string _connectionString = "你的数据库连接字符串";

    public User GetUserById(int id)
    {
        // 这里写原生SQL查询逻辑,比如用SqlConnection、SqlCommand
        User user = null;
        using (var conn = new SqlConnection(_connectionString))
        {
            conn.Open();
            var cmd = new SqlCommand("SELECT Id, Username, Phone FROM Users WHERE Id = @Id", conn);
            cmd.Parameters.AddWithValue("@Id", id);
            using (var reader = cmd.ExecuteReader())
            {
                if (reader.Read())
                {
                    user = new User
                    {
                        Id = (int)reader["Id"],
                        Username = reader["Username"].ToString(),
                        Phone = reader["Phone"].ToString()
                    };
                }
            }
        }
        return user;
    }

    public bool AddUser(User user)
    {
        // 执行Insert的原生SQL逻辑
        using (var conn = new SqlConnection(_connectionString))
        {
            conn.Open();
            var cmd = new SqlCommand("INSERT INTO Users (Username, Phone) VALUES (@Username, @Phone)", conn);
            cmd.Parameters.AddWithValue("@Username", user.Username);
            cmd.Parameters.AddWithValue("@Phone", user.Phone);
            return cmd.ExecuteNonQuery() > 0;
        }
    }

    // Update、Delete方法同理,都放在这里
}

如果把CRUD逻辑塞到Model里,会让Model既当「数据容器」又当「数据操作器」,后期维护起来会非常麻烦——比如改数据库连接要动Model,改业务规则也要动Model,耦合度太高了。而抽离到Repository后,Presenter只需要调用Repository的方法,完全不用关心数据是怎么读写的,逻辑清晰很多。

二、Model该在哪里实例化?

分两种场景来看:

  • 读取数据时:Model的实例化应该放在Repository的方法里(比如上面GetUserById方法里new User并赋值),Presenter只需要调用repo.GetUserById()直接拿到现成的Model实例就行,不用自己手动new。
  • 创建/更新数据时:推荐在Presenter里实例化Model——View只负责收集用户的输入(比如文本框内容),然后把这些原始值传给Presenter,由Presenter来组装成完整的Model,再交给Repository去保存。这样View的职责更纯粹,只做展示和输入收集,不处理数据组装的逻辑。

另外,关于实例化的位置,还有两个小建议:

  • 不要在每个方法里重复实例化相同的依赖(比如Repository),可以在Presenter的构造函数里初始化一次,整个Presenter生命周期内复用;
  • 尽量避免在View里直接实例化Model,不然会让View和Model的耦合度变高,违反MVP的分层思想。

给你看个Presenter的示例片段:

public class UserPresenter
{
    private readonly IUserView _userView;
    private readonly UserRepository _userRepo;

    // 在Presenter构造函数里初始化依赖
    public UserPresenter(IUserView userView)
    {
        _userView = userView;
        _userRepo = new UserRepository(); // 也可以用依赖注入替代手动new,更灵活
    }

    public void LoadUser(int userId)
    {
        // 从Repository获取现成的Model实例
        User user = _userRepo.GetUserById(userId);
        if (user != null)
        {
            _userView.ShowUsername(user.Username);
            _userView.ShowPhone(user.Phone);
        }
        else
        {
            _userView.ShowNotFoundMessage();
        }
    }

    public void SaveNewUser()
    {
        // 从View拿输入,在Presenter里组装Model
        User newUser = new User
        {
            Username = _userView.GetInputUsername(),
            Phone = _userView.GetInputPhone()
        };

        bool saveSuccess = _userRepo.AddUser(newUser);
        if (saveSuccess)
        {
            _userView.ShowSaveSuccess();
        }
        else
        {
            _userView.ShowSaveError();
        }
    }
}

这样整个流程的职责就很清晰了:View管展示和输入,Presenter管逻辑调度,Repository管数据读写,Model只负责存数据。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.19 09:57:51