原文:IEditableObject的一个通用实现

IeditableObject是一个通用接口,用于支持对象编辑。当我们在界面上选择一个条目,然后对其进行编辑的时候,接下来会有两种操作,一个是保持编辑结果,一个取消编辑。这就要求我们保留原始值,否则我们只能到数据库里面再次查询。IeditableObject接口的三个方法定义为我们定义了这个行为规范:

public
interface
IEditableObject

{

//
开始编辑,一般在此方法内创建当前对象副本

void BeginEdit();

//取消编辑,当副本恢复到当前对象,并清除副本

void CancelEdit();

//
接受编辑结果,并清除副本

void EndEdit();

}

对于IeditableObject的实现,应该满足一下要求:

  1. 具有NonEditableAttribute标记的属性不参与编辑

  2. 如果某个属性类型也实现了IeditableObject,
    那么将递归调用相应编辑方法。

  3. 对于集合对象,如果集合对象实现了IeditableObject,将会对集合的每个项调用相应编辑方法。

  4. 可以查询对象是否改变,包括任何标量属性的变化,关联的IeditableObject类型的属性的变化,集合属性的变化。

下面是具体实现:

首先要定义NonEditableAttribute类:

[AttributeUsage(AttributeTargets.Property,Inherited
= true, AllowMultiple =
false)]

public
sealed
class
NonEditableAttribute :
Attribute {}

其次是一个辅助类,用于找到一个类型内的标量属性,可编辑对象属性和集合属性,因为这三种属性需要不同的处理方式:

internal
class
EditableProperty

{

public EditableProperty(Type
type)

{

if (type ==
null)

{

throw
new
ArgumentNullException("type");

}

Scalars = new
List<PropertyInfo>();

Editables = new
List<PropertyInfo>();

Collections = new
List<PropertyInfo>();

foreach (var
property in type.GetProperties(BindingFlags.Public
| BindingFlags.Instance))

{

//忽略定义了NonEditableAttribute的属性。

if (property.IsDefined(typeof(NonEditableAttribute),
false))

{

continue;

}

//不能读的属性不参与编辑

if (!property.CanRead)

{

continue;

}

Type propertyType = property.PropertyType;

if (propertyType.IsValueType || propertyType ==
typeof(string))

{

//标量属性需要是值类型或者string类型,并且可写。

if (property.CanWrite)

{

Scalars.Add(property);

}

}

//可编辑对象属性是递归参与编辑流程的。

else
if ((typeof(IEditableObject).IsAssignableFrom(propertyType)))

{

Editables.Add(property);

}

//集合属性也是参与编辑流程的。

else
if (typeof(IList).IsAssignableFrom(propertyType))

{

Collections.Add(property);

}

}

}

public
List<PropertyInfo>
Scalars { get;
private
set; }

public
List<PropertyInfo>
Editables { get;
private
set; }

public
List<PropertyInfo>
Collections { get;
private
set; }

}

下面是可编辑对象的实现:

[Serializable]

public
abstract
class
EditableObject :
NotifiableObject,
IEditableObject

{

//缓存可编辑属性,不用每次重新获取这些元数据

private
static
ConcurrentDictionary<Type,
EditableProperty> _cachedEditableProperties;

static EditableObject()

{

_cachedEditableProperties = new
ConcurrentDictionary<Type,
EditableProperty>();

}

//对象的副本

private
object _stub;

private
bool _isEditing;

//对象是不是处于编辑状态。

[NonEditable]

public
bool IsEditing

{

get {
return _isEditing; }

protected
set

{

if (_isEditing !=
value)

{

_isEditing = value;

base.OnPropertyChanged("IsEditing");

}

}

}

//获取对象是不是改变了,比如说,调用了BeginEdit但是并没有修改任何属性,对象就没有改变,

//此时不需要保存,检查修改的时候内部做了对象相互引用造成的无穷递归情况。所以即使对象有相互应用

//也能正确检测。

[NonEditable]

public
bool IsChanged

{

get

{

return GetIsChanged(new
HashSet<EditableObject>());

}

}

//开始编辑

public
void BeginEdit()

{

//如果已经处于编辑状态,那么什么也不做。

if (IsEditing)

{

return;

}

IsEditing = true;

//创建对象副本。

if (this
is
ICloneable)

{

ICloneable cloneable =
this
as
ICloneable;

_stub = cloneable.Clone();

}

else

{

_stub = MemberwiseClone();

}

var editableProp = GetEditableProperty();

//对于每个管理的IeditableObject,递归调用BeginEdit

foreach (var
item in editableProp.Editables)

{

var editableObject = item.GetValue(this,
null)
as
IEditableObject;

if (editableObject !=
null)

{

editableObject.BeginEdit();

}

}

//对于集合属性中,如果任何项是IeditableObject,那么递归调用BeginEdit。

foreach (PropertyInfo
collProperty in editableProp.Collections)

{

IList coll = collProperty.GetValue(this,
null)
as
IList;

if (coll !=
null)

{

foreach (IEditableObject
editableObject in coll.OfType<IEditableObject>())

{

editableObject.BeginEdit();

}

}

}

}

//取消编辑

public
void CancelEdit()

{

//如果没有处于编辑状态,就什么也不做。

if (!IsEditing)

{

return;

}

IsEditing = false;

var editableProp = GetEditableProperty();

//还原标量属性的值。

foreach (PropertyInfo
scalarProperty in editableProp.Scalars)

{

scalarProperty.SetValue(this,scalarProperty.GetValue(_stub,
null),
null);

}

//对于IeditableObject属性,递归调用CancelEdit

foreach (PropertyInfo
editableProperty in editableProp.Editables)

{

IEditableObject editableObject = editableProperty.GetValue(this,
null)
as
IEditableObject;

if (editableObject !=
null)

{

editableObject.CancelEdit();

}

}

foreach (PropertyInfo
collProperty in editableProp.Collections)

{

IList collOld = collProperty.GetValue(_stub,
null)
as
IList;

IList collNew = collProperty.GetValue(this,
null)
as
IList;

//如果两个集合不相同,那么就恢复原始集合的引用。

if (!object.ReferenceEquals(collOld,
collNew))

{

collProperty.SetValue(this, collOld,
null);

}

//对原始集合中每个IeditableObject,递归调用CancelEdit

if (collOld !=
null)

{

foreach (IEditableObject
editableObject in collOld.OfType<IEditableObject>())

{

editableObject.CancelEdit();

}

}

}

//清除副本

_stub = null;

}

public
void EndEdit()

{

//如果没有处于编辑状态,就什么也不做。

if (!IsEditing)

{

return;

}

IsEditing = false;

var editableProp = GetEditableProperty();

//对于每个IeditableObject属性,递归调用EndEdit

foreach (PropertyInfo
editableProperty in editableProp.Editables)

{

IEditableObject editableObject = editableProperty.GetValue(this,
null)
as
tableObject;

if (editableObject !=
null)

{

editableObject.EndEdit();

}

}

//对于集合属性中每个项,如果其是IeditableObject,则递归调用EndEdit

foreach (PropertyInfo
collProperty in editableProp.Collections)

{

IList collNew = collProperty.GetValue(this,
null)
as
IList;

if (collNew !=
null)

{

foreach (IEditableObject
editableObject in collNew.OfType<IEditableObject>())

{

editableObject.EndEdit();

}

}

}

//清除副本

_stub = null;

}

private
bool GetIsChanged(HashSet<EditableObject>
markedObjects)

{

//如果没有在编辑状态,那么表示对象没有改变

if (!IsEditing)

{

return
false;

}

//如果对象已经被检查过了,说明出现循环引用,并且被检查过的对象没有改变。

if (markedObjects.Contains(this))

{

return
false;

}

var editableProp = GetEditableProperty();

//检测标量属性有没有变化。

foreach (PropertyInfo
scalarProperty in editableProp.Scalars)

{

object newValue = scalarProperty.GetValue(this,
null);

object oldValue = scalarProperty.GetValue(_stub,
null);

bool changed =
false;

if (newValue !=
null)

{

changed =!newValue.Equals(oldValue);

}

else
if (oldValue !=
null)

{

changed = true;

}

if (changed)

{

return
true;

}

}

//标记此对象已经被检查过

markedObjects.Add(this);

//对于每一个IeditableObject属性,进行递归检查

foreach (PropertyInfo
editableProperty in editableProp.Editables)

{

EditableObject editableObject = editableProperty.GetValue(this,
null)
as
EditableObject;

if (editableObject !=
null)

{

if (editableObject.GetIsChanged(markedObjects))

{

return
true;

}

}

}

//检查集合对象的想等性

foreach (PropertyInfocollectionProperty
in editableProp.Collections)

{

IList empty =
new
object[0];

IList collOld = (collectionProperty.GetValue(_stub,
null)
as
IList) ?? empty;

IList collNew = (collectionProperty.GetValue(this,
null)
as
IList) ?? empty;

if (!object.ReferenceEquals(collOld,
collNew))

{

//Detectif elements are added or deleted in Collection.

if (!collOld.Cast<object>().SequenceEqual(collNew.Cast<object>()))

{

return
true;

}

}

//Detectif any element is changed in collection.

foreach (var
item in collNew)

{

EditableObject editableObject
= item as
EditableObject;

if (editableObject !=
null)

{

if (editableObject.GetIsChanged(markedObjects))

{

return
true;

}

}

}

}

return
false;

}

private
EditableProperty GetEditableProperty()

{

return _cachedEditableProperties.GetOrAdd(GetType(), t =>
new
EditableProperty(t));

}

}

在WPF程序里面,大部分业务对象都要实现InotifyPropertyChanged以便数据绑定,所以我们实现了这个接口,并让EditableObject从这个实现派生,从而让Editableobject也具有绑定支持。NotifiableObject类非处简单,如下:

[Serializable]

public
abstract
class
NotifiableObject :
INotifyPropertyChanged

{

private
const
string ERROR_MSG =
"{0}is not a public property of {1}";

private
static
readonly
ConcurrentDictionary<string,
PropertyChangedEventArgs> _eventArgCache;

static NotifiableObject()

{

//缓存PropertyChangedEventArgs,以提高性能。

_eventArgCache = new
ConcurrentDictionary<string,
PropertyChangedEventArgs>();

}

[field:
NonSerialized]

public
event
PropertyChangedEventHandler PropertyChanged;

public
static
PropertyChangedEventArgs GetPropertyChangedEventArgs(string
propertyName)

{

if (string.IsNullOrEmpty(propertyName))

{

throw
new
ArgumentException("propertyName
cannotbe null or empty.");

}

return _eventArgCache.GetOrAdd(propertyName, p =>
new
PropertyChangedEventArgs(p));

}

protected
void OnPropertyChanged([CallerMemberName]string
propertyName = "")

{

VerifyProperty(propertyName);

PropertyChangedEventHandler handler = PropertyChanged;

if (handler !=
null)

{

var args = GetPropertyChangedEventArgs(propertyName);

handler(this, args);

}

}

[Conditional("DEBUG")]

private
void VerifyProperty(string
propertyName)

{

Type type = GetType();

PropertyInfo propInfo = type.GetProperty(propertyName);

if (propInfo ==
null)

{

Debug.Fail(string.Format(ERROR_MSG,
propertyName, type.FullName));

}

}

}

下面的单元测试代码对EditableObject进行的简单的测试:

[TestClass]

public
class
EditableObjectTest

{

[TestMethod]

public
void UseEditableObject_WithoutCallingMethodsOfIEditableObject()

{

User u =
new
User() { Name =
"john", Age = 20, Wage = 200 };

Assert.AreEqual(u.Name,
"john");

Assert.AreEqual(u.Age, 20);

Assert.AreEqual(u.Wage, 200);

Assert.IsFalse(u.IsChanged);

Assert.IsFalse(u.IsEditing);

u.Age = 21;

u.Wage = 250;

Assert.AreEqual(u.Name,
"john");

Assert.AreEqual(u.Age, 21);

Assert.AreEqual(u.Wage, 250);

}

[TestMethod]

public
void BeginEdit_EndEdit_ScalarProperties()

{

User u =
new
User() { Name =
"john", Age = 20, Wage = 200 };

u.BeginEdit();

Assert.IsFalse(u.IsChanged);

Assert.IsTrue(u.IsEditing);

u.Age = 21;

Assert.IsTrue(u.IsChanged);

Assert.IsTrue(u.IsEditing);

u.EndEdit();

Assert.AreEqual(u.Age, 21);

Assert.IsFalse(u.IsEditing);

Assert.IsFalse(u.IsChanged);

}

[TestMethod]

public
void BeginEdit_CancelEdit_ScalarProperties()

{

User u =
new
User() { Name =
"john", Age = 20, Wage = 200 };

u.BeginEdit();

u.Wage = 250;

u.CancelEdit();

Assert.IsFalse(u.IsEditing);

Assert.IsFalse(u.IsChanged);

Assert.AreEqual(u.Wage, 200);

}

[TestMethod]

public
void BeginEdit_EndEdit_EditableProperties()

{

DateTime start =
new
DateTime(2000, 1, 1);

DateTime newStart =
new
DateTime(2000, 1, 2);

User u =
new
User() { Name =
"john", Age = 20, Wage = 200 };

Task t =
new
Task() { Name =
"writereports", StartTime = start, Owner =u };

u.Task = t;

u.BeginEdit();

Assert.IsTrue(u.IsEditing);

Assert.IsFalse(u.IsChanged);

Assert.IsTrue(t.IsEditing);

Assert.IsFalse(t.IsChanged);

t.StartTime = newStart;

Assert.IsTrue(u.IsEditing);

Assert.IsTrue(u.IsChanged);

Assert.IsTrue(t.IsEditing);

Assert.IsTrue(t.IsChanged);

u.EndEdit();

Assert.AreEqual(t.StartTime, newStart);

Assert.IsFalse(u.IsEditing);

Assert.IsFalse(u.IsChanged);

Assert.IsFalse(t.IsEditing);

Assert.IsFalse(t.IsChanged);

}

[TestMethod]

public
void BeginEdit_CancelEdit_EditableProperties()

{

DateTime start =
new
DateTime(2000, 1, 1);

DateTime newStart =
new
DateTime(2000, 1, 2);

User u =
new
User() { Name =
"john", Age = 20, Wage = 200 };

Task t =
new
Task() { Name =
"writereports", StartTime = start, Owner =u };

u.Task = t;

u.BeginEdit();

t.StartTime = newStart;

u.CancelEdit();

Assert.AreEqual(t.StartTime, start);

Assert.IsFalse(u.IsEditing);

Assert.IsFalse(u.IsChanged);

Assert.IsFalse(t.IsEditing);

Assert.IsFalse(t.IsChanged);

}

[TestMethod]

public
void BeginEdit_EndEdit_CollectionProperties_WithModify()

{

User u =
new
User() { Name =
"john", Age = 20, Wage = 200 };

var item =
new
SettingItem { Name =
"setting1", Value =
"10" };

u.Settings = new
List<SettingItem>();

u.Settings.Add(item);

u.BeginEdit();

Assert.IsTrue(u.IsEditing);

Assert.IsFalse(u.IsChanged);

Assert.IsTrue(item.IsEditing);

Assert.IsFalse(item.IsChanged);

item.Value = "20";

Assert.IsTrue(u.IsEditing);

Assert.IsTrue(u.IsChanged);

Assert.IsTrue(item.IsEditing);

Assert.IsTrue(item.IsChanged);

u.EndEdit();

Assert.AreEqual(item.Value,
"20");

Assert.IsFalse(u.IsEditing);

Assert.IsFalse(u.IsChanged);

Assert.IsFalse(item.IsEditing);

Assert.IsFalse(item.IsChanged);

}

[TestMethod]

public
void BeginEdit_CancelEdit_CollectionProperties_WithModify()

{

User u =
new
User() { Name =
"john", Age = 20, Wage = 200 };

var item =
new
SettingItem { Name =
"setting1", Value =
"10" };

u.Settings = new
List<SettingItem>();

u.Settings.Add(item);

u.BeginEdit();

item.Value = "21";

u.CancelEdit();

Assert.AreEqual(item.Value,
"10");

Assert.IsFalse(u.IsEditing);

Assert.IsFalse(u.IsChanged);

Assert.IsFalse(item.IsEditing);

Assert.IsFalse(item.IsChanged);

}

[TestMethod]

public
void BeginEdit_EndEdit_CollectionProperties_WithAdd()

{

User u =
new
User() { Name =
"john", Age = 20, Wage = 200 };

u.Settings = new
List<SettingItem>();

u.BeginEdit();

var item =
new
SettingItem { Name =
"setting1", Value =
"10" };

u.Settings.Add(item);

Assert.IsTrue(u.IsEditing);

Assert.IsTrue(u.IsChanged);

Assert.IsFalse(item.IsEditing);

Assert.IsFalse(item.IsChanged);

u.EndEdit();

Assert.AreEqual(u.Settings[0].Value,
"10");

Assert.IsFalse(u.IsEditing);

Assert.IsFalse(u.IsChanged);

Assert.IsFalse(item.IsEditing);

Assert.IsFalse(item.IsChanged);

}

[TestMethod]

public
void BeginEdit_EndEdit_CollectionProperties_WithDelete()

{

User u =
new
User() { Name =
"john", Age = 20, Wage = 200 };

var item =
new
SettingItem { Name =
"setting1", Value =
"10" };

u.Settings = new
List<SettingItem>();

u.Settings.Add(item);

u.BeginEdit();

u.Settings.Clear();

Assert.IsTrue(u.IsEditing);

Assert.IsTrue(u.IsChanged);

u.EndEdit();

Assert.AreEqual(u.Settings.Count, 0);

Assert.IsFalse(u.IsEditing);

Assert.IsFalse(u.IsChanged);

}

}

class
User :
EditableObject,
ICloneable

{

public
string Name {
get;
set; }

public
decimal Wage {
get;
set; }

public
int Age {
get;
set; }

public
Task Task {
get;
set; }

public
List<SettingItem>
Settings { get;
set; }

public
object Clone()

{

User u = MemberwiseClone()
as
User;

if (Settings !=
null)

{

u.Settings = new
List<SettingItem>(Settings);

}

return u;

}

}

class
Task :
EditableObject

{

public
string Name {
get;
set; }

public
DateTime StartTime {
get;
set; }

public
User Owner {
get;
set; }

}

class
SettingItem :
EditableObject

{

public
string Name {
get;
set; }

public
string Value {
get;
set; }

}



IEditableObject的一个通用实现的更多相关文章

  1. 编写一个通用的Makefile文件

    1.1在这之前,我们需要了解程序的编译过程 a.预处理:检查语法错误,展开宏,包含头文件等 b.编译:*.c-->*.S c.汇编:*.S-->*.o d.链接:.o +库文件=*.exe ...

  2. Linux C编程学习之开发工具3---多文件项目管理、Makefile、一个通用的Makefile

    GNU Make简介 大型项目的开发过程中,往往会划分出若干个功能模块,这样可以保证软件的易维护性. 作为项目的组成部分,各个模块不可避免的存在各种联系,如果其中某个模块发生改动,那么其他的模块需要相 ...

  3. 封装一个通用递归算法,使用TreeIterator和TreeMap来简化你的开发工作。

    在实际工作中,你肯定会经常的对树进行遍历,并在树和集合之间相互转换,你会频繁的使用递归. 事实上,这些算法在逻辑上都是一样的,因此可以抽象出一个通用的算法来简化工作. 在这篇文章里,我向你介绍,我封装 ...

  4. 一个通用的DataGridView导出Excel扩展方法(支持列数据格式化)

    假如数据库表中某个字段存放的值“1”和“0”分别代表“是”和“否”,要在DataGridView中显示“是”和“否”,一般用两种方法,一种是在sql中直接判断获取,另一种是在DataGridView的 ...

  5. 利用RBAC模型实现一个通用的权限管理系统

    本文主要描述一个通用的权限系统实现思路与过程.也是对此次制作权限管理模块的总结. 制作此系统的初衷是为了让这个权限系统得以“通用”.就是生产一个web系统通过调用这个权限系统(生成的dll文件), 就 ...

  6. 为了去重复,写了一个通用的比较容器类,可以用在需要比较的地方,且支持Lamda表达式

    为了去重复,写了一个通用的比较容器类,可以用在需要比较的地方,且支持Lamda表达式,代码如下: public class DataComparer<T>:IEqualityCompare ...

  7. 用Java实现一个通用并发对象池

    这篇文章里我们主要讨论下如何在Java里实现一个对象池.最近几年,Java虚拟机的性能在各方面都得到了极大的提升,因此对大多数对象而言,已经没有必要通过对象池来提高性能了.根本的原因是,创建一个新的对 ...

  8. 一个通用数据库访问类(C#,SqlClient)

    本文转自:http://www.7139.com/jsxy/cxsj/c/200607/114291.html使用ADO.NET时,每次数据库操作都要设置connection属性.建立connecti ...

  9. Java反射结合JDBC写的一个通用DAO

    以前写反射只是用在了与设计模式的结合上,并没有考虑到反射可以与DAO结合.也是一个偶然的机会,被正在上培训的老师点到这个问题,才考虑到这个可能性,于是上网参考各种代码,然后自己动手开发了一个通用DAO ...

随机推荐

  1. 7、linux之定时器

    (1) 一个timer_list 结构体的实例对应一个定时器,其定义如下: struct timer_list { struct list_head entry, /*定时器列表*/ unsigned ...

  2. 浅浅的分析下es6箭头函数

    原文链接:http://damobing.com/?p=589 前言 箭头函数作为es6重点的语法内容之一,很多开发者对其爱不释手,当也要注意其可能存在的问题,其正确的使用场景,否则会引起不必要的bu ...

  3. Activex调试以及m_hWnd为空 解决办法

    1. 点击[开始]->[运行] 命令:regedit.2. 定位到HKEY_LOCALMACHINE -> SOFTWARE -> Microsoft -> Internet ...

  4. WM_CAP_DRIVER_CONNECT

    WM_CAP_DRIVER_CONNECT //ActiveX ---->OnCreate m_pit.Create(IDD_CAM_DIALOG,this);  CRect rc;  this ...

  5. 【BZOJ 1096】[ZJOI2007]仓库建设

    [链接] 链接 [题意] 在这里输入题意 [题解] 设f[i]表示在第i个地方设立一个仓库,且前面符合要求的最小花费. 则 \(f[i] = min(f[j] + c[i] + dis[i]*(sum ...

  6. swift学习:自定义Log

    import UIKit /* 总结:1:let file = (#file as NSString).lastPathComponent,#file获取的是打印所在的文件 的全路径,转成NSStri ...

  7. php课程 3-12 回调参数怎么用

    php课程 3-12 回调参数怎么用 一.总结 一句话总结:有时候需要在一个函数中使用另外一个函数,使用回调的话,那么那个函数的几十行代码就不用写了,其实很基础,就是函数名的字符串的替换,超级基础的. ...

  8. php 文件夹是否存在,不存在就创建

    $lujing = "./nihao/wohao"; if(!is_dir($liujing)){ mkdir(iconv("UTF-8", "GBK ...

  9. 《SAS编程与数据挖掘商业案例》学习笔记之十二

    本次重点在:sas数据集管理 主要包含:包含数据集纵向拼接.转置.排序.比較.复制.重命名.删除等 1.append语句 注:base数据集和data两个数据集必须结构一样.避免使用force的情况, ...

  10. 学习金字塔 分类: T_TALENT 2014-05-21 09:25 331人阅读 评论(0) 收藏

    学习金字塔是美国缅因州的国家训练实验室研究成果,它用数字形式形象显示了:采用不同的学习方式,学习者在两周以后还能记住内容(平均学习保持率)的多少.它是一种现代学习方式的理论.最早它是由美国学者.著名的 ...