原文: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. 一起学JAVA之《spring boot》03 - 开始spring boot基本配置及项目结构(转)

    <div class="markdown_views"> <h3 id="一导航"><a name="t0"& ...

  2. 账号被盗!请勿在CSDN,回复不论什么消息。

    账号被盗!请勿在CSDN,回复不论什么消息.

  3. Bootstrap相关优质项目必备网址

    1:文档全集:这里收集了Bootstrap从V1.0.0版本到现在,整个文档的历史.Bootstrap本身就是一个传奇,而这些文档就是传奇的见证! 官方网址:http://docs.bootcss.c ...

  4. Codeforces 467C. George and Job

    DP.... C. George and Job time limit per test 1 second memory limit per test 256 megabytes input stan ...

  5. php实现求链表中倒数第k个节点

    php实现求链表中倒数第k个节点 一.总结 $head = $head->next; //1.将$head节点next域里面的记录的那个地址值($head节点的下一个节点的地址)给$head,$ ...

  6. YUV与RGB格式转换

    YUV格式具有亮度信息和色彩信息分离的特点,但大多数图像处理操作都是基于RGB格式. 因此当要对图像进行后期处理显示时,需要把YUV格式转换成RGB格式. RGB与YUV的变换公式如下: YUV(25 ...

  7. 使用Perl分割文件

    使用Perl分割文件 特性 使用换行作为分界 忽略注释行# 分割存入新指定的文件中 待分割的文件test.lst wwdg/prescaler syscfg/test1 syscfg/test2 ua ...

  8. Instruments性能优化-Core Animation

    简书地址:http://www.jianshu.com/users/6cb2622d5eac/latest_articles 当App发展到一定的规模.性能优化就成为不可缺少的一点.可是非常多人,又对 ...

  9. #308 (div.2) B. Vanya and Books

    1.题目描写叙述:点击打开链接 2.解题思路:本题要求统计数位的个数,简单的试验一下发现有例如以下规律:一个n位数的个数有9*(10^n)个.因此全部n位数的数位是n*9*(10^n)个.因此能够利用 ...

  10. http500:服务器内部错误案例详解(服务器代码语法错误或者逻辑错误)

    http500:服务器内部错误案例详解(服务器代码语法错误或者逻辑错误) 一.总结 服务器内部错误可能是服务器中代码运行的时候的语法错误或者逻辑错误 二.http500:服务器内部错误案例详解 只是一 ...