我只是在学习MVVM,而我正在努力研究如何根据计算出来的值的变化来显示对计算属性的更改。到目前为止我看到的所有解决方案都严重违反了封装,我想知道是否有更好的解决方案。
假设我需要显示的一件事是复杂税收计算的结果。计算(可能还有它的依赖关系)会不时发生变化,所以我想把它严格封装起来。
这里最经常提供的解决方案似乎是获取税值所依赖的所有属性,以便在ModelView中为属性本身以及依赖于它的每个属性调用PropertyChanged。这意味着每个属性都需要知道使用或可能使用它的所有内容。当我的税收规则以一种使计算依赖于以前不依赖的事物的方式改变时,我将需要触及所有进入我计算的新属性(可能在其他类中,可能不在我的控制之下),让他们为税值调用PropertyChanged。这完全破坏了封装的任何希望。
我能想到的最佳解决方案是让进行计算的类接收 PropertyChanged事件,并在进行计算的任何更改时为税值引发新的PropertyChanged事件。这至少保留了 class 级别的封装,但它仍然违反了方法封装:类不应该知道方法如何工作。
所以,我的问题是,是否有更好的方法(如果有的话,它是什么)?或者表示封装(MVVM)是否阻止了业务逻辑的封装?我是否面临过任何一种选择?
答案 0 :(得分:4)
这里最经常提供的解决方案似乎是获得所有的解决方案 税值所依赖的属性调用PropertyChanged 属性本身和每个属性的ModelView 取决于它。
除非显示支持属性,否则支持属性不需要自己的更改通知。但是每个房产都需要直接在其设置者中调用税值OnPropertyChanged("TaxValue")
;或根据以下示例间接地。这样,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包:
或者如果不使用标准OnPropetyChanged("TaxValue")
。
答案 1 :(得分: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的公式引擎。
我在域和视图模型类的几个项目中使用它,它帮助我消除了大多数命令式控制流(错误的主要来源),并使代码更具说明性和清晰。
关于它的最好的事情是依赖属性可以属于不同的视图模型,并且依赖图在运行时可以发生显着变化,它仍然可以正常工作。
答案 2 :(得分:3)
有一个名为Fody/PropertyChanged的插件在编译时工作,可以自动实现PropertyChanged
。当一个复杂的税收计算发生变化时,它会自动查看同一个类中的哪些属性使用您的属性并引发所有相应的PropertyChanged
事件。
您可以使用ILSpy对已编译的代码进行反编译,以查看其执行的操作并验证它是否会引发所有相应的事件。
答案 3 :(得分:3)
这里最常提供的解决方案似乎是获取税值所依赖的所有属性,以便在ModelView中为属性本身以及依赖于它的每个属性调用PropertyChanged。 ....
是的,但仅限于该对象:每个属性都应在setter中触发自己的属性更改事件。此外,setter应该以某种方式触发依赖于该值的属性。您不应尝试主动触发其他对象的更新:它们应该侦听此对象PropertyChanged
。
我能想到的最佳解决方案是让进行计算的类接收PropertyChanged事件,并在进入计算的任何更改时为税值引发新的PropertyChanged事件。这至少保留了类级别的封装,但它仍然违反了方法封装:类不应该知道方法如何工作。
这确实是标准方式。每个类都有责任监视它所依赖的属性,并为其属性触发属性更改事件。
可能有一些框架可以帮助你做到这一点,但是值得知道应该发生什么。
答案 4 :(得分:0)
我能想到的最好的解决方案就是让这个课程成功 计算接收 PropertyChanged事件,并引发新的事件 当任何更改时,TaxChanged事件为税值 进入计算。这至少可以保留封装 class 级别,但它仍然违反方法封装:类不应该知道方法如何工作。
我认为你正在将术语“封装”扩展到对语法的争论。这里没有问题,例如:
private int _methodXCalls;
public void MethodX() {
Console.WriteLine("MethodX called {0} times", ++_methodXCalls);
}
该字段仅在MethodX中相关,但仅仅因为声明在语法上 MethodX
并不意味着它会破坏方法封装。
同样,在类初始化中为每个属性设置事件处理程序也没有问题。只要它只在初始化时出现一次,并且不需要“知道”那些特定的处理程序被添加,你的属性仍然在逻辑上是自包含的。您可能以某种方式使用属性上的属性,例如[DependsOn(property1, property2)]
,但这只是一个代码可读性问题。