个人原创,如有雷同,实为巧合。转载请注明:最近闲来无事又东搞西搞。。。 2017-03-09 17:30
我在做桌面小程序时经常见到这样的场景:
选中一条数据,弹出个框啥的修改,有[确定]和[取消]两个选项,点取消之后所有变更都不能应用到原有的那条数据上。
手动去控制,也就是每次都写一大堆代码去控制,着实很烦,Model里面层数多了,很不好控制,但也感觉没啥好办法。
后来我想,能不能写个接口啥的控制一下,修改界面的那边,保留对原数据的引用,再创建一个一模一样的拷贝(里面与原数据无任何相同引用),所有的操作都只针对拷贝出来的数据进行,不然就会出现这边改了的同时原有数据所在页面也跟着变了的情况。
如果最终选择了[取消],那就什么都不用管;如果选择了[确定],就用拷贝出来的数据来“完全替换”原有数据,注意这里并不能直接给个等号,这样会导致对原数据的引用丢失,所做的操作也就没有用了。
于是接口长这样:
public interface IUIModel<T> where T : class, new() { T Clone(); void UpdateBy(T other); }栗子:
public class SomeModel : IUIModel<SomeModel> { public int SomeProperty { get; set; } public SomeModel Clone() { SomeModel copy = new SomeModel(); copy.SomeProperty = this.SomeProperty; return copy; } public void UpdateBy(SomeModel other) { Assert.NotNull(other); this.SomeProperty = other.SomeProperty; } }有点实用价值的栗子:
public class AnotherModel : IUIModel<AnotherModel> { public string AnotherProperty { get; set; } public SomeModel SomeModel { get; set; } public AnotherModel Clone() { AnotherModel copy = new AnotherModel(); copy.AnotherProperty = this.AnotherProperty; copy.SomeModel = this.SomeModel.Clone(); return copy; } public void UpdateBy(AnotherModel other) { Assert.NotNull(other); this.AnotherProperty = other.AnotherProperty; this.SomeModel.UpdateBy(other.SomeModel); } }对,消灭引用!
再后来吧,我突然发现有重复的部分:
public AnotherModel Clone() { AnotherModel copy = new AnotherModel(); copy.UpdateBy(this); return copy; }然后又感觉这部分可以用个抽象类来搞,少写点代码。
于是就出现了父类子类相互转换这种被其他程序员喷来喷去的奇葩玩意。。。
首先因为数据库的原因,需要加两个属性到父类:
public long Pkey { get; private set; } public State State { get; private set; }其中 State 是个枚举:Normal, Draft, Removed
可以放在父类中的方法有 Clone 和 针对上面两个属性的 UpdateBy,子类中其他字段的 UpdateBy 就在子类中去实现。
于是这个泛型抽象类就出现了:
public abstract class UIModelBase<T> where T : class, new() { public long Pkey { get; private set; } public State State { get; private set; } public T Clone() { T copy = Activator.CreateInstance<T>(); (copy as UIModelBase<T>).UpdateBy(this); return copy; } public void UpdateBy(UIModelBase<T> other) { this.Pkey = other.Pkey; this.State = other.State; this.updateBy(other as T); } protected abstract void updateBy(T other); }大概解释下,
Activator.CreateInstance<T>()这句创建了 T 的实例,而 where T : new() 确保它有一个公共的无参构造函数,使得创建实例这部分基本没有问题。
而后面的
(copy as UIModelBase<T>).UpdateBy(this);则是子类转为父类,再调用父类的 UpdateBy 方法。
接下来的 UpdateBy 方法就有些“奇怪”了。
// 传进来的参数类型是父类 UIModelBase<T>,但是 this.updateBy(other as T) 这句话却把父类转成了子类。一般情况下,子类是可以转成父类的,但是父类转子类却无法实现,因为子类中额外的部分父类是不知道的,就算转过去这些额外的部分就丢了。
但是这里能转成功的原因就是,UpdateBy 方法传进来的“父类”其实就是子类的一个实例,把“子类”转成相同的类型当然可以了,因为抽象类本身是没有实例的。
像下面这样,假设有 A 和 B 两个类:
public class A { } public class B : A { public int AB { get; set; } }然后:
A a_1 = new A(); B b_1= a_1 as B; Assert.Null(b_1); A a_2 = new B(); B b_2 = a_2 as B; Assert.NotNull(b_2);解释完了,看下(啰嗦的)栗子:
public class SomeModel : UIModelBase<SomeModel> { public int SomeProperty { get; set; } protected override void updateBy(SomeModel other) { Assert.NotNull(other, "SomeModel.updateBy.other"); this.SomeProperty = other.SomeProperty; } } public class AnotherModel : UIModelBase<AnotherModel> { public string AnotherProperty { get; set; } public SomeModel SomeModel { get; set; } protected override void updateBy(AnotherModel other) { Assert.NotNull(other, "AnotherModel.updateBy.other"); this.AnotherProperty = other.AnotherProperty; if (this.SomeModel == null) { if (other.SomeModel != null) { this.SomeModel = other.SomeModel.Clone(); } } else { if (other.SomeModel != null) { this.SomeModel.UpdateBy(other.SomeModel); } else { this.SomeModel = null; } } } }测试:
AnotherModel left = new AnotherModel(); left.AnotherProperty = "left"; left.SomeModel = new SomeModel(); left.SomeModel.SomeProperty = 0; Assert.True(left.SomeModel.SomeProperty == 0); AnotherModel right = left; right.AnotherProperty = "right"; right.SomeModel.SomeProperty = 1; Assert.True(left.SomeModel.SomeProperty == 1); AnotherModel rightCopy = left.Clone(); rightCopy.AnotherProperty = "right copy"; rightCopy.SomeModel.SomeProperty = 2; Assert.True(left.SomeModel.SomeProperty == 1);当我给同事推荐这个基类时,果然还是遭到了鄙视。。。
需要栗子! Chestnuts is required!
接下来用一个很平常的简单 wpf 例子来看下实际应用。
将之前的UIModelBase稍作改动:
其中ViewModelBase为很普通的Mvvm接口实现:
在之前随意写过的SomeModel和AnotherModel中都加上:
然后是两个界面,第一个界面显示个AnotherModel的列表,在另一个界面编辑其中某条数据。
第一个界面中的列表Binding如下图:
后台ViewModel:
首先是Source
其中 这句是我写的一个简单拓展
点击其中一条记录时,将那条数据传给编辑页面ViewModel:
其中Navigate是个用来页面导航的事件
第二个界面的Binding如下:
后台:
首先是用来保存引用的Model还有实际进行修改的Model
其中用来保留引用的给个等号,实际进行修改的调用Clone方法,并将其作为绑定到界面的Model。
剩下的就是取消和确认修改的操作:
Cancel和Confirm两个操作最后都要导航到列表界面,唯一不同的就是确定操作中执行了更新引用数据但又不不会造成引用丢失的UpdateBy方法:
看下实际运(jiè)行(miàn)结(fēng)果(gé)
modified at 2017-03-10 14:30