1. 程式人生 > >IEditableObject的一個通用實現

IEditableObject的一個通用實現

原文: 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類非處簡單,如下:


[<