哪一层应该包含 ICommand?

Which layer should contain ICommand?

在 WPF 中,我有一个名为 Malfunctions 的 ViewModel class,它有一个 PartMalfunctions 的 ObservableCollection。通常,ObservableCollection 中有 10 到 15 个 PartMalfunction 对象;有多少取决于超出该问题范围的其他参数。

我有一些 xaml 具有绑定到此 ObservableCollection 的 DataGrid。在 DataGrid 中,我显示了 PartMalfuction 的各种属性(即描述、名称等),并且我有一个用户可以单击的开始计时器按钮。启动计时器按钮绑定到 PartMalfunction 模型 class 中的 ICommand StopwatchCmd(您可以在下面的代码中看到所有这些)。

这是我的问题:我的 StopwatchCmd 是否在错误的层中(即 - 它是否属于故障 ViewModel)? 我真的很难解决这个问题并尝试我自己弄明白了,但我一直在碰壁,可以这么说,因为模型 class 中的 StopwatchCmd 工作得很好!我的意思是它能够在那里执行并执行它需要的任何业务规则,并且只与它触发的对象实例进行交互。如果我将它放在 ViewModel 中,那么我似乎必须做更多的工作才能让它完成它已经在做的事情。

请注意,我遗漏了故障 ViewModel 中的一些代码,因为它与这个问题无关。这是故障视图模型的代码。

public class Malfunctions : ViewModelBase {
       public ObservableCollection<Model.PartMalfunction> AllPartMalfunctions {
            get;
            private set;
        }
}

PartMalfunction 的模型 class 看起来像这样:

public class PartMalfunction : INotifyPropertyChanged {
    public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;

    private void OnPropertyChanged(string propertyName) {
        if (PropertyChanged != null) {
            PropertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName));
        }
    }

    private int _seconds;
    private string _stopwatchText = string.Empty;
    private bool _isStopwatchInProgress = false;
    System.Windows.Threading.DispatcherTimer _timer = new System.Windows.Threading.DispatcherTimer();
    RelayCommand _stopwatchCmd;

    public ICommand StopwatchCmd {
        get {
            if (_stopwatchCmd == null)
                _stopwatchCmd = new RelayCommand(param => this.StopwatchClick());
            return _stopwatchCmd;
        }
    }
    public bool IsStopwatchInProgress {
        get {
            return _isStopwatchInProgress;
        }
        set {
            _isStopwatchInProgress = value;
            OnPropertyChanged("IsStopwatchInProgress");
        }
    }
    public string StopwatchText {
        get {
            return _stopwatchText;
        }
        set {
            _stopwatchText = value;
            OnPropertyChanged("StopwatchText");
        }
    }
    private void StopwatchClick() {

        if (!this.IsStopwatchInProgress) {
            // Start the timer
            _seconds = 0;

            // Will immediately update the timer text to "00:00:00"
            this.StopwatchText = GetElapsed();

            _timer.Tick += DispatcherTimer_Tick;
            _timer.Interval = new TimeSpan(0, 0, 1); // Ticks every second
            _timer.Start();

            this.IsStopwatchInProgress = true;
        }
        else {
            // Stop the timer
            _timer.Stop();
            _timer.Tick -= DispatcherTimer_Tick;
            _seconds = 0;

            this.IsStopwatchInProgress = false;
        }
    }
    private void DispatcherTimer_Tick(object sender, System.EventArgs e) {
        _seconds += 1;

        this.StopwatchText = GetElapsed();
    }
    private string GetElapsed() {
        int hour = 0, min = 0, sec = 0;

        if (_seconds > 59) {
            min = (int)_seconds / 60;
            sec = _seconds % 60;

            if (min > 59) {
                hour = (int)min / 60;
                min = min % 60;
            }
        }
        else
            sec = _seconds;

        string elapsed = hour < 10 ? "0" + hour.ToString() : hour.ToString();
        elapsed += ":" + (min < 10 ? "0" + min.ToString() : min.ToString());
        elapsed += ":" + (sec < 10 ? "0" + sec.ToString() : sec.ToString());

        return elapsed;
    }
}

这个问题可以被视为主要基于意见,但我相信它有助于经验较少的开发人员理解模型-视图-视图模型的边界。

对我来说,你认为的模型实际上是一个 ViewModel,更具体地说,父 ViewModel (Malfunctions) 有一个子 ViewModel (PartMalfunction) 的集合,作为一个集合 (ObservableCollection) 公开,这意味着没有PartMalfunction class.

上的 ICommand 属性存在问题

如果我发现一个模型 class 为显示做了大量的数据格式(文本、日期等)那么它更有可能是一个 ViewModel,这种事情是 ViewModel 的责任.同样对我来说,模型 class 没有实现 INotifyPropertyChanged 接口,通知是使用事件(或 Rx 流)完成的,然后订阅者(ViewModel)可以选择如何以及何时更新 UI.

以下是我在 MVVM 中的组织方式:

  • 模型:仅实体,类 的部分被消耗。
  • 视图:Xaml 及其背后的代码可能 show/manipulate 视图模型提供的模型。
  • ViewModel :该层是 业务 逻辑所在的位置,并且存储从数据库检索到的数据(模型)。它是视图和模型之间的 管道 。它可以访问数据库、创建计时器、保存 ICommands……只要 what 仅与业务逻辑相关。没有直视处理。

还记得当时风靡一时的三层系统吗?如果您将 MVVM 视为可能有帮助的三层。恕我直言


Which layer should contain ICommand?

视图模型,因为它处理命令的业务逻辑。将来,当一个人拿起代码来维护它时,大多数开发人员首先会看的是那里。

ICommand 有两个有趣的成员。 Execute,它定义了另一个对象可以请求命令所有者执行的操作,CanExecute,它定义了另一个对象是否应该请求该操作。这在 ICommand 的属性名称中非常明显,但如果仔细观察,它们会 ICommand 在 ViewModel 层中找到一个完美的家。您的视图模型可以将一些操作公开给未知视图,并控制它们何时被允许执行。由于命令通常是视图模型上的 public 属性,因此您可以轻松地从 WPF 控件绑定到它们,而无需任何紧密耦合。 Button 例如,可以绑定到一个命令来定义它的作用,而无需知道它的 DataContext(视图模型)是什么类型。

如果您尝试将 ICommand 放在您的视图中,您可能会发现与方法相比它们会很麻烦,因为视图可以访问它们自己的方法。正因为如此,ICommand不太适合放在View层。

由于您的视图模型将模型从您的模型中抽象出来(理想情况下),您将无法(也不想)在您的模型上放置命令 类。任何处理被呈现或与之交互的逻辑都不属于模型层。