适当的使用懒加载可以改善耗时操作的性能,AOP通过拦截访问的字段和属性以及将样板代码移到单独的切面类中使得对位置进行懒加载不再那么痛苦。字段或属性可能会遇到样板代码问题的其它地方在可响应的GUI。
真实案例——INotifyPropertyChanged 在桌面应用中使用INotifyPropertyChanged首先创建一个WPF应用,搭建的界面如下图所示:
需求是,当输入进行输入时,需要将姓和名两个文本框中的内容连接起来填充到姓名那行所在的Label控件上。在WPF中一种普遍的做法是创建一个封装数据(姓和名)和导出数据(姓名)的视图模型。创建视图模型NameViewModel如下:
public class NameViewModel { public string FirstName { get; set; } public string LastName { get; set; } public string FullName { get { return string.Format("{0}{1}", FirstName, LastName); } } }还需要做以下几步才能实现需求:
第一步:
public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); DataContext=new NameViewModel(); } }第二步,第三步:
<StackPanel Orientation="Horizontal"> <Label Content="姓:" Width="100"/> <TextBox Height="23" Width="200" Text="{Binding Path=FirstName,UpdateSourceTrigger=PropertyChanged}" /> </StackPanel> <StackPanel Orientation="Horizontal"> <Label Content="名:" Width="100"/> <TextBox Height="23" Width="200" Text="{Binding Path=LastName,UpdateSourceTrigger=PropertyChanged}" /> </StackPanel>第四步:
public class NameViewModel:INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; void OnPropertyChanged(string propertyName) { if (PropertyChanged!=null) { PropertyChanged(this,new PropertyChangedEventArgs(propertyName)); } } private string _firstName; public string FirstName { get { return _firstName; } set { if (value!=_firstName) { _firstName = value; OnPropertyChanged("FirstName"); OnPropertyChanged("FullName"); } } } private string _lastName; public string LastName { get { return _lastName; } set { if (value!=_lastName) { _lastName = value; OnPropertyChanged("LastName"); OnPropertyChanged("FullName"); } } } public string FullName { get { return string.Format("{0}{1}", FirstName, LastName); } } }如果你对WPF熟悉的话,那么上面的代码没什么可说的:无论何时在这些属性上使用了set,PropertyChanged事件都会被触发。比如,在姓的文本框上输入了A,那么就会导致FirstName的属性值被设置set。在set期间,触发了两次PropertyChanged:一次是宣布FirstName属性修改了,然后是宣布FullName属性修改了。
编译、运行程序,结果如下:
虽然这个例子不是很复杂,但是在真实的WPF应用中,可能会有更多的字段以及这些字段之间关系更复杂,如果熟悉MVVM(Model-View-ViewModel)模式的话,那么这种类型的绑定对于实现那种模式很重要。此外,虽然这只是一个简单的示例,但是NameViewModel从一个只有自动属性的小类变得越来越大,代码越来越多,有了支持字段,而且每个set方法中还要逻辑。虽然可以在View和ViewModel之间进行干净的分离,但是使用INotifyPropertyChanged会面临很多陷阱和问题。
使用INotifyPropertyChanged的问题和约束虽然使用INotifyPropertyChanged有很多好处,但是也有很多弊端,比如,潜在产生了样板代码,脆弱的代码以及可能维护起来困难的代码。
产生的样板代码很明显,因为从最原始的只有三个自动属性的NameViewModel类现在体积已经膨胀了好几倍,有了显式的支持字段,setter里面也有了逻辑,因此,这里有很多重复,可以使用AOP减少重复。
其次,要触发PropertyChanged事件,就需要有一个PropertyChangedEventArgs对象,它需要一个字符串来识别已经改变的属性。因此,当每次调用OnPropertyChanged时,需要传一个和属性名称对应的字符串,如果不小心手误输错了,就会导致触发事件失败。
最后,使用INotifyPropertyChanged很难维护。因为它使用了字符串,如果更改了属性名,就必须记得也要修改字符串(安装了ReSharp等重构工具时,如果重命名属性,ReSharp可以帮我们完成这件事)。还要注意,因为我们有一个导出属性(FullName),所以要记得当发送关于其它属性更改的消息时要包括该属性。
使用ReSharp重构
虽然属性名FirstName和字符串“FirstName”对于我们人类来说看起来是相同的,但是对于编译器它们是不同的符号,如果更改了一个符号,编译器不会聪明到也能意识到其它相关的符号,当运行代码时最终会报错。
一些重构工具比如ReSharp,Telerik JustCode等都会尝试使用智能分析和演绎找出相关的符号。比如,当使用ReSharp重命名FirstName属性时,它可能会询问你是否想要更改“FirstName”字符串的值。