【问题标题】:MVVM - Model vs ViewModel - where to put my code?MVVM - 模型与视图模型 - 将我的代码放在哪里?
【发布时间】:2021-03-02 17:20:58
【问题描述】:

我正在创建一个用于管理在线赛车联赛的小型应用程序。 为此,我将拥有一个从 Web 访问数据并通过接口公开数据对象的数据库。该数据库尚不存在,但我创建了一个使用本地 XML 文件作为数据源的 Mockup。

联赛界面的小例子:

public interface ISchedule
{
    string Name { get; set; }
    List<IRaceSession> Races { get; }
    // and some more properties …

    IRaceSession AddRace();
    void RemoveRace(IRaceSession race);
    // and some more Methods …
}

public interface IRaceSession
{
    uint RaceId { get; }
    DateTime Date { get; set; }
    TimeSpan Duration { get; set; }
    // and some more properties …
}

现在,为了使用 MVVM 模式将其放入我的 WPF 中,我为数据库公开的每个对象创建了一个模型,并在那里实现了 INPC。 *注意:ContainerModel&lt;&gt;ObservableModelCollection&lt;&gt; 是我创建的类,用于处理来自数据库的更新,同时仍保持 INPC 完整。

public class ContainerModel<T>
{
    T Source { get; set; }

    public ContainerModel(T source)
    {
        Source = source;
    }

    void UpdateSource(T source)
    {
        // handle updates …
    }
}

public class ScheduleModel : ISchedule, ContainerModel<ISchedule>
{
    public string Name { get => Source.Name ; set { Source.Name = value; NotifyPropertyChanged(); } }

    public ObservableModelCollection<RaceSessionModel, IRaceSession> Races { get; }
    List<IRaceSession> ISchedule.Races => Source.Races

    public ScheduleModel(ISchedule source) : base(source)
    {
        Races = new ObservableModelCollection<RaceSessionModel, IRaceSession>(Source.Races);
    }

    IRaceSession AddRace()
    {
        Races.Add(// new Race Object);
    }
    void RemoveRace(IRaceSession race)
    {
        Races.Remove(// select race object to remove);
    }
}

public class RaceSessionModel : IRaceSession, ContainerModel<IRaceSession>
{
    public uint RaceId => Source.RaceId;
    puglic DateTime Date { get => Source.Date; set { Source.Date = value; NotifyPropertyChanged(); } }
    TimeSpan Duration { get => Source.Duration; set { Source.Duration = value; NotifyPropertyChanged(); } }
    

    //--> here come some more logic im not sure About:
    TimeSpan DurationHours 
    { 
        get => Duration.Hours;
        set 
        {
            // set only Hours componennt of Duration
            Duration = Duration
                .Subtract(Duration.Hours)
                .Add(value);
            NotifyPropertyChanged();
        }

    TimeSpan DurationMinutes 
    { 
        get => Duration.Minutes;
        set 
        {
            // set only Minutes componennt of Duration
            Duration = Duration
                .Subtract(Duration.Minutes)
                .Add(value);
            NotifyPropertyChanged();
        }
}

然后我有一个 viewModel 和一个直接绑定到 Model 属性的视图。

public class SchedulerViewModel : ViewModelBase //<-- just a Basic implementation of INPC
{
    private ScheduleModel _schedule;
    public ScheduleModel Schedule { get => _schedule; set { _schedule = value; NotifyPropertyChanged(); } }

    public SchedulerViewModel(ScheduleModel schedule)
    {
        Schedule = schedule;
    }

    // Commands and other properties communicating with the view
    // …
}

现在有了这个设计,我有一些担忧。我仍在摸索整个设计模式,这是我第一次从头开始构建它。

  1. 我的模型并不真正包含数据,而只是公开数据库中的属性 我是否认为这实际上是 viewModel 应该做的事情?

  2. 如您所见,我的模型还对属性输入进行了某种计算。这种“业务逻辑”也应该放在模型之外吗?还是将其放入 ViewModel 中会更好?

毕竟我怀疑我所呈现的是“模型”。将它们称为 ViewModel,然后将数据库中的 Object 作为模型进行操作是否正确?

*编辑: 当我从这个开始时,我读到对于每个View,你应该只提供一个ViewModel,这就是我创建这样的类的原因。但是我不确定这是否正确。

【问题讨论】:

  • 在这种情况下,您也可以保持简单:将 IRaceSession 和 ISchedule 视为模型,其他所有内容(实际上并不多)都放在 ViewModel 中。我不明白你为什么会为了它而在两者之间添加一个额外的层(实际上什么都不做)。
  • @stijn:谢谢。这基本上就是我的问题要解决的问题,但我担心当应用程序最终变得更加复杂时,这种方法是否会反击。
  • 可能,但是您以后总是可以重构,而且很容易做到。请参阅 KISS 和 YAGNI 原则:如果简单、干净、可读的代码现在可以完成这项工作,那么在我看来,您必须有 非常 充分的理由不使用它。我已经编写了相当多的 MVVM 代码并回顾过去,试图严格遵守 MVVM 或不承认模型是什么(或严格遵守“模型”的一个定义),通常只是数据的表示,导致过于复杂。

标签: c# wpf mvvm


【解决方案1】:

这可能很大程度上取决于您尝试对应用程序执行的操作,但通常我会这样处理它:

  • 时间表模型
  • RaceSession 模型
  • RaceSession 的 ViewModel,包含 RaceSession 模型
  • ViewModel for Schedule 包含 Schedule Model 的集合 RaceSessionViewModels

本质上,我一直认为模型几乎只是代表数据库中的一行;在应用程序之外独立存在的基本数据实体。 ViewModel 就是任何只在应用程序中相关的东西。

我不会在 Schedule Model 中收集 RaceSession 模型的原因是,如果您要对 RaceSessions 进行任何类型的基于应用程序的操作,那是属于 ViewModel 的东西,所以您d 然后有点看一个带有 RaceSessionViewModels 集合的调度模型。所以我将模型严格地保留为单个数据实体,没有任何类型的实体关系(连接)内置 - 这些类型的关系,我将内置到 ViewModel 层。即使您不需要 RaceSession 的 ViewModel,我仍然会在 Schedule ViewModel 中拥有一组 RaceSession 模型。

作为上面的例子,我认为AddRace方法确实属于Schedule ViewModel,而不是Schedule Model。

关于TimeSpan 计算,我可能将小时和分钟作为get 唯一属性(在RaceSession ViewModel 上),而RaceSessionViewModel 上的其他方法直接改变Duration 属性。具体的实现取决于您在数据库更新时是否实际更改了它们。

一些示例伪代码

public class RaceSession : INPC
{
    INPCProperties

    RaceSession(inpcProperties)
    {
        INPCProperties = inpcProperties;
    }
}

public class RaceSessionViewModel : INPC
{
    public RaceSession RaceSession { get; set (INPC); }

    public int Hours => RaceSession.Duration.Hours;

    public RaceSessionViewModel(raceSession)
    {
        RaceSession = raceSession;
    }

    private void SetDurationHours(int hours)
    {
        RaceSession.Duration =
            Duration
            .Subtract(Duration.Hours)
            .Add(hours);

        NotifyPropertyChanged("Hours");
    }
}

【讨论】:

  • 这实际上是我的第一个设计概念。但是我遇到了一些问题: 1. 这意味着 id 必须编写大量重复代码,因为 id 必须首先将每个属性从数据库转发到模型,然后通过带有 INPC 的 VM。 2. 数据库可能会定期向模型发布更新。如果模型没有实现 INPC 而不会在我的虚拟机和数据库之间创建不必要的耦合,我如何能够相应地更新 ViewModel。
  • 我并不是建议您从视图中将属性读入 ViewModel。我仍然会在模型上实现 INPC,并且我仍然会在适当的情况下直接绑定到模型属性(我在我的答案中添加了一些伪代码,可能会说明这一点)。关于更新,有不同的方法可以做到这一点,例如,如果检测到更改,则更新单个模型属性,如果检测到更改,则刷新/替换整个模型,定期或根据请求刷新整个 RaceSession 集合,所以这将取决于您需要如何实现。
  • 更正:我上一条评论的第一部分应该是“我并不是建议您从 Model 将属性读入 ViewModel。”
【解决方案2】:

为了将数据从模型传输到视图模型,我将创建没有(或非常少)逻辑的简单对象(Dto - DataTransferObject)。 一个好的经验法则是,您希望 ViewModel 中有未保存的数据,而保存的数据(或即将保存的数据)属于模型。

//--> here come some more logic im not sure About:
TimeSpan DurationHours 
{ 
    get => Duration.Hours;
    set 
    {
        // set only Hours componennt of Duration
        Duration = Duration
            .Subtract(Duration.Hours)
            .Add(value);
        NotifyPropertyChanged();
    }

TimeSpan DurationMinutes 
{ 
    get => Duration.Minutes;
    set 
    {
        // set only Minutes componennt of Duration
        Duration = Duration
            .Subtract(Duration.Minutes)
            .Add(value);
        NotifyPropertyChanged();
    }

我会将这些放入 ViewModel。在您想要整个 Timespan 对象的模型中,只有 ViewModel 应该知道您只能在 View 中单独设置它们的限制。

当我从这个开始时,我读到你应该为每个视图提供 只有一个 ViewModel,这就是我创建这样的类的原因。但 我不确定这是否正确。

是的,当您必须维护代码并进行仅适用于单个视图的更改时,它可以省去很多麻烦。

【讨论】:

  • 一个好的经验法则是,您希望 ViewModel 中未保存的数据,而保存的数据(或即将保存的数据)属于模型。有一个例外对于像窗口这样的向导,它有自己的有状态虚拟机,我希望数据在编辑后立即“准备好保存”。那么在这种情况下,我将状态排除在虚拟机之外的方法是正确的吗?
【解决方案3】:
  1. 没有。视图模型将数据(模型而不是数据库)与视图连接起来。但它既不操纵视图也不操纵数据库。
  2. 我不会把它留在模型中。这种东西实际上属于视图模型。 在每个视图中,您可以有多个视图模型来呈现,但您肯定需要至少一个开始。 我建议,因为你已经开始构建你的 MVVM,你现在在 Internet 的某个地方查找一个实现此设计模式的示例。我想你已经阅读了它背后的一些理论。

【讨论】:

    猜你喜欢
    • 2012-09-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-12-14
    • 2018-02-01
    相关资源
    最近更新 更多