11

我只是在学习 MVVM,我正在尝试如何显示计算属性的更改,因为计算属性的值发生了变化。到目前为止,我看到的所有解决方案都严重违反了封装,我想知道是否有更好的解决方案。

假设我需要显示的一件事是复杂税收计算的结果。计算(可能还有它的依赖项)会不时改变,所以我想严格封装它。

此处最常提供的解决方案似乎是获取税值所依赖的所有属性,以便在 ModelView 中为属性本身和依赖它的每个属性调用 PropertyChanged 。这意味着每个属性都需要知道使用或可能使用它的所有内容。当我的税收规则发生变化,使得计算依赖于它以前不依赖的事物时,我需要触及所有那些进入我的计算的新属性(可能在其他类别中,可能不在我的控制之下),以让他们为税值调用 PropertyChanged。这完全破坏了封装的任何希望。

我能想到的最佳解决方案是让进行计算的类接收PropertyChanged 事件,并在计算中发生任何变化时为税收值引发一个新的 PropertyChanged 事件。这至少保留了级别的封装,但它仍然违反了方法封装:类不必知道方法如何工作。

所以,我的问题是,有没有更好的方法(如果有,它是什么)?或者表示封装(MVVM)会阻止业务逻辑的封装?我面临一个非此即彼的选择吗?

4

5 回答 5

4

这里最常提供的解决方案似乎是获取税值所依赖的所有属性,以便在 ModelView 中为属性本身和依赖它的每个属性调用 PropertyChanged。

不,支持属性不需要自己的更改通知,除非它们正在显示。但是每个属性都需要OnPropertyChanged("TaxValue")直接在他们的 setter(s) 中调用税收值;或间接根据下面的示例。这样,由于支持属性已更改,因此 UI 会更新。

话虽如此,让我们考虑一个例子。一种方法是创建一种方法来计算值。当设置最终值(下面的 TaxValue)时,它将调用OnNotifyPropertyChange. 该操作会将 TaxValue 更改通知给整个世界的用户;无论是什么值触发它(扣除|费率|收入):

public class MainpageVM : INotifyPropertyChanged 
{
       public decimal TaxValue 
        {
           get { return _taxValue; }
           set { _taxValue = value; OnPropertyChanged(); }  // Using .Net 4.5 caller member method.
        }

        public decimal Deduction
        {
           get { return _deduction; }
           set { _deduction = value; FigureTax(); }
        }

        public decimal Rate
        {
           get { return _rate; }
           set { _rate = value; FigureTax(); }
        }

        public decimal Income
        {
           get { return _income; }
           set { _income = value; FigureTax(); }
        }

        // Something has changed figure the tax and update the user.
        private void FigureTax()
        {
           TaxValue = (Income - Deduction) * Rate;
        }


    #region INotifyPropertyChanged
        public event PropertyChangedEventHandler PropertyChanged;

        /// <summary>Raises the PropertyChanged event.</summary>
        /// <param name="propertyName">The name of the property that has changed, only needed
        /// if called from a different source.</param>
        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChangedEventHandler handler = PropertyChanged;
            if (handler != null)
            {
                handler(this, new PropertyChangedEventArgs(propertyName));
            }
        }

    #endif
    }

编辑

要在 .Net 4 中使用 CallerMemberName(和其他项目),请安装 Nuget 包:

微软.BCL

或者如果不使用标准OnPropetyChanged("TaxValue")

于 2013-10-25T14:14:06.047 回答
4

查看 Stephen Cleary 的计算属性:https ://github.com/StephenCleary/CalculatedProperties

它非常简单,就是这样做的:传播依赖属性的通知而不污染触发器属性设置器。

原始示例:

public string Name 
{
  get { return Property.Get(string.Empty); }
  set { Property.Set(value); }
} 

public string Greeting => Property.Calculated(() => "Hello, " + Name + "!");

它的大小非常强大:想想 View Model 属性的类似 Excel 的公式引擎。

我在域和视图模型类的几个项目中使用了它,它帮助我消除了大部分命令式控制流(错误的主要来源),并使代码更具声明性和清晰性。

最好的一点是依赖属性可以属于不同的视图模型,并且依赖图可以在运行时发生巨大变化,它仍然可以正常工作。

于 2017-01-03T13:04:17.037 回答
3

这里最常提供的解决方案似乎是获取税值所依赖的所有属性,以便在 ModelView 中为属性本身和依赖它的每个属性调用 PropertyChanged。……

是的,但仅限于该对象:每个属性都应在 setter 中触发其自己的属性更改事件。此外,setter 应该以某种方式触发依赖于自身值的属性。您不应该尝试主动触发其他对象的更新:它们应该监听这个对象PropertyChanged

我能想到的最佳解决方案是让进行计算的类接收 PropertyChanged 事件,并在计算中发生任何变化时为税收值引发一个新的 PropertyChanged 事件。这至少保留了类级别的封装,但它仍然违反了方法封装:类不必知道方法如何工作。

这确实是标准的方式。每个类都有责任监视它所依赖的属性,并为它的属性触发属性更改事件。

可能有一些框架可以帮助您做到这一点,但值得知道应该发生什么。

于 2013-10-25T14:17:02.903 回答
3

有一个名为Fody/PropertyChanged的​​插件,它在编译时工作以自动实现PropertyChanged. PropertyChanged当一个复杂的税收计算发生变化时,它将自动查看同一类中的哪些属性使用您的属性并引发所有适当的事件。

您可以使用ILSpy反编译已编译的代码,以查看它做了什么并验证它是否引发了所有适当的事件。

于 2013-10-25T13:57:55.923 回答
0

我能想到的最佳解决方案是让进行计算的类接收PropertyChanged 事件,并在计算中发生任何变化时为税收值引发一个新的 PropertyChanged 事件。这至少保留了 级别的封装,但它仍然违反了方法封装:类不必知道方法如何工作。

我认为您将“封装”一词扩展到对语法的质疑。这里没有问题,例如:

private int _methodXCalls;
public void MethodX() {
    Console.WriteLine("MethodX called {0} times", ++_methodXCalls);
}

该字段仅在 MethodX 中相关,但仅仅因为声明在语法上不在内部 MethodX并不意味着它破坏了方法封装。

同样,在类初始化中为每个属性设置事件处理程序也没有问题。只要它在初始化时只出现一次,并且不需要“知道”那些特定的处理程序被添加,你的属性在逻辑上仍然是独立的。您可能会以某种方式在属性上使用属性,例如[DependsOn(property1, property2)],但这实际上只是代码可读性问题。

于 2013-10-25T14:48:57.283 回答