Kevin-moon

学习在于分享
  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

如何重构代码-简单、灵活的实现对象复制

Posted on 2010-05-14 08:51  Kevin-moon  阅读(4892)  评论(13编辑  收藏  举报

  前两篇(思路和方法重构计划)从大的方面上谈了关于重构的话题,这次从小的代码上来看。我们来看下一个的代码如何从简单到复杂,然后重构这些代码。

 

单个对象复制


   在初步的需求中有个很简单的业务,就是定义销售合同,并且合同中可以配置产品设备数据,如下:

  其中有个业务功能就是需要对已经存在的销售合同进行复制、剪贴和粘贴的工作。

  对于程序来说,它其实就需要实现IClone接口就可以了,

代码
/// <summary>
/// 复制
/// </summary>
public object Clone()
{
    TestObj copiedObj 
= new TestObj();
    copiedObj.Parent 
= null;
    copiedObj.Name 
= this.Name;
    
foreach (TestObj obj in this.Child)
    {
        TestObj childCopiedObj 
= obj.Clone() as TestObj;
        childCopiedObj.Parent 
= copiedObj;
        copiedObj.Child.Add(childCopiedObj);
    }
    
return copiedObj;
}

   OK,这很好的实现了业务需求。

 

批量复制不同对象


  业务需求发生了变化,现在对于前期简单的销售合同进行了完善,同时让它变得异常复杂,如下图,

  现在要求在所有内部对象都支持复制、剪贴和粘贴,并且也要支持每个部分的数据可以独立导出为特殊的文件,作为数据模板,对于其他合同为了简化操作可以将该模块导入,这些动作在实现前都必须要执行复制,前面的动作不用说都可以理解,对于后面的导入和导出来说,虽然主要通过序列化来支持,但是由于业务特殊性也必须先做复制操作,然后需要对复制后的数据做特殊处理,然后才去序列化。

  对于这次的需求来说,很多人的实现方式是对每个类都模仿Clone的方式来写自己的复制方法,这种方式一定是正确的,并且性能是最好的,不过它也是代码量最多的,同时随着业务的进一步完善,它的代码会越来越多。

  按照上述的方式来实现,可能需要两周左右的时间,可以想想有没有办法减少开发时间呢?而且也让以后的开发时间减少?

  想了很久,终于有了一种思路:

具体的实现代码看下这三个类的代码,

1、最核心的类CopyContainer

代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;

namespace Kevin.CommonFunction.Clone
{
    
/// <summary>
    
/// 复制元素的容器
    
/// </summary>
    
/// <remarks>
    
/// 执行批量的复制,默认只复制对象的值类型,引用关系保留
    
/// </remarks>
    public class CopyContainer
    {
        
#region 属性

        
/// <summary>
        
/// 上下文环境参数
        
/// </summary>
        private Dictionary<stringobject> mContextParams;

        
/// <summary>
        
/// 等待被复制的对象
        
/// </summary>
        private List<object> mWaitToCopiedItems = new List<object>();

        
/// <summary>
        
/// 被复制对象-新对象的索引关系
        
/// </summary>
        private Dictionary<objectobject> mItemIndexs = new Dictionary<objectobject>();

        
/// <summary>
        
/// 构造函数的缓存
        
/// </summary>
        private Dictionary<Type, ConstructorInfo> mConstcCache = new Dictionary<Type, ConstructorInfo>();

        
/// <summary>
        
/// 字段的缓存
        
/// </summary>
        private Dictionary<Type, List<FieldInfo>> mFieldInfoCache = new Dictionary<Type, List<FieldInfo>>();

        
/// <summary>
        
/// 复制后的新对象
        
/// </summary>
        private List<object> mNewItems = new List<object>();

        
#endregion

        
#region 构造函数

        
/// <summary>
        
/// 
        
/// </summary>
        public CopyContainer()
        { 
            
        }

        
#endregion

        
#region 方法

        
/// <summary>
        
/// 添加等待被复制的对象
        
/// </summary>
        
/// <param name="waitCopiedItem">需要复制的对象</param>
        public void AddToWaitCopiedItems(object waitCopiedItem)
        {
            
if (waitCopiedItem != null && !mWaitToCopiedItems.Contains(waitCopiedItem))
            {
                
this.mWaitToCopiedItems.Add(waitCopiedItem);
            }
        }

        
/// <summary>
        
/// 添加环境参数
        
/// </summary>
        
/// <param name="key">环境中的唯一主键</param>
        
/// <param name="contextParam">环境参数</param>
        public void AddToContextParams(string key, object contextParam)
        {
            
if (mContextParams == null)
            {
                mContextParams 
= new Dictionary<stringobject>();
            }

            
this.mContextParams.Add(key, contextParam);
        }

        
/// <summary>
        
/// 复制对象
        
/// </summary>
        
/// <returns>所有复制后的对象列表</returns>
        public List<object> Clone()
        {
            
if (this.mWaitToCopiedItems == null)
            {
                
return null;
            }

            
foreach (object obj in this.mWaitToCopiedItems)
            {
                
object newObj = CopyToNewObj(obj);

                mItemIndexs.Add(obj, newObj);

                mNewItems.Add(newObj);
            }

            
foreach (object newObj in mNewItems)
            {
                ICopyReference copiedObj 
= newObj as ICopyReference;
                
if (copiedObj != null)
                {
                    copiedObj.SetReference(
this.mContextParams, this.mItemIndexs);
                }
            }
            
            
return this.mNewItems;
        }

        
/// <summary>
        
/// 复制对象
        
/// </summary>
        
/// <param name="copeidItem">被复制的对象</param>
        
/// <returns>复制后的对象</returns>
        private object CopyToNewObj(object copeidItem)
        {
            Type copiedType 
= copeidItem.GetType();

            ConstructorInfo constc 
= this.GetConstructor(copiedType);

            List
<FieldInfo> fields = this.GetFieldInfos(copiedType);

            
object newObj = constc.Invoke(null);
            
foreach (FieldInfo field in fields)
            {
                
object copiedValue = field.GetValue(copeidItem);
                field.SetValue(newObj, copiedValue);
            }

            
return newObj;
        }

        
/// <summary>
        
/// 获取构造函数 
        
/// </summary>
        
/// <param name="copiedType">被复制对象的类型</param>
        
/// <returns>构造函数</returns>
        private ConstructorInfo GetConstructor(Type copiedType)
        {
            ConstructorInfo copiedConstc 
= null;

            
if (this.mConstcCache.ContainsKey(copiedType))
            {
                copiedConstc 
= this.mConstcCache[copiedType];
            }
                
//该构造函数未加入缓存
            else
            {
                ConstructorInfo[] constructors 
= copiedType.GetConstructors(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public);

                
//循环所有构造函数
                foreach (ConstructorInfo constc in constructors)
                {
                    ParameterInfo[] paramsInConst 
= constc.GetParameters();

                    
if (paramsInConst.Length == 0)
                    {
                        copiedConstc 
= constc;
                        
break;
                    }
                }

                
if (copiedConstc == null)
                {
                    
throw new Exception(string.Format("类 '{0}' 缺少无参的构造函数", copiedType.FullName));
                }

                
this.mConstcCache.Add(copiedType, copiedConstc);
            }

            
return copiedConstc;
        }

        
/// <summary>
        
/// 获取字段
        
/// </summary>
        
/// <param name="copiedType">被复制对象的字段</param>
        
/// <returns>字段</returns>
        private List<FieldInfo> GetFieldInfos(Type copiedType)
        {
            List
<FieldInfo> fields = null;

            
if (this.mFieldInfoCache.ContainsKey(copiedType))
            {
                fields 
= this.mFieldInfoCache[copiedType];
            }
                
//该字段未加入缓存
            else
            {
                fields 
= new List<FieldInfo>();
                
                FieldInfo[] fieldsInCT 
= copiedType.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
                
foreach (FieldInfo fieldItem in fieldsInCT)
                {
                    
//过滤带有KeepFixedValueAttribute自定义属性的字段
                    if (fieldItem.GetCustomAttributes(typeof(KeepFixedValueAttribute), true).Length == 0)
                    {
                        fields.Add(fieldItem);
                    }
                }

                
this.mFieldInfoCache.Add(copiedType, fields);
            }

            
return fields;
        }

        
#endregion
    }
}

2、复制对象引用的操作接口ICopyReference

代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Kevin.CommonFunction
{
    
/// <summary>
    
/// 复制对象引用的操作接口
    
/// </summary>
    
/// <remarks>
    
/// 当在CopyContainer中执行复制的时候,对于复制对象引用的支持
    
/// /// </remarks>
    public interface ICopyReference
    {
        
/// <summary>
        
/// 设置复制对象的引用关系
        
/// </summary>
        
/// <param name="contextParams">上下文环境参数</param>
        
/// <param name="mItemIndexs">被复制对象-新对象的索引关系</param>
        void SetReference(Dictionary<stringobject> contextParams, Dictionary<objectobject> mItemIndexs);
    }
}

3、用于该复制操作的自定义属性KeepFixedValueAttribute

代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Kevin.CommonFunction.Clone
{
    
/// <summary>
    
/// 当在CopyContainer中执行复制的时候,保留对象特定字段的初始值
    
/// </summary>
    public class KeepFixedValueAttribute : Attribute
    {
    }
}

 

上述是所有的实现代码,下面看下是如何使用的:

代码
using System;
using System.Text;
using System.Collections.Generic;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;

using Kevin.CommonFunction.Clone;
using Kevin.CommonFunction;

namespace Kevin.NUnitTest.Clone
{
    [TestClass]
    
public class T_CopyContainer
    {
        [TestMethod]
        
public void Clone()
        {
            CopyContainer cc 
= new CopyContainer();

            TestObj oldObj 
= new TestObj();
            cc.AddToWaitCopiedItems(oldObj);
            List
<object> newItems = cc.Clone();

            Assert.AreEqual(newItems.Count, 
2);
        }
    }

    
public class TestObj : ICopyReference
    {
        
/// <summary>
        
/// 自动生成的主键
        
/// </summary>
        public int mKey = System.Guid.NewGuid().GetHashCode();

        
/// <summary>
        
/// 上级对象
        
/// </summary>
        public TestObj Parent;

        
/// <summary>
        
/// 下级对象列表
        
/// </summary>
        public List<TestObj> Child = new List<TestObj>();

        
/// <summary>
        
/// 名称
        
/// </summary>
        public string Name;

        
/// <summary>
        
/// 设置复制对象的引用关系
        
/// </summary>
        
/// <param name="contextParams">上下文环境参数</param>
        
/// <param name="mItemIndexs">被复制对象-新对象的索引关系</param>
        public void SetReference(Dictionary<stringobject> contextParams, Dictionary<objectobject> mItemIndexs)
        {
            List
<object> arrCopiedItems = new List<object>();

            
foreach (TestObj obj in this.Child)
            {
                
if (mItemIndexs.ContainsKey(obj))
                {
                    arrCopiedItems.Add(mItemIndexs[obj]);
                }
                
else
                {
                    arrCopiedItems.Add(obj);
                }
            }
        }
    }
}

   虽然,每个支持复制操作的对象都需要实现ICopyReference接口,这是为了处理对象内的引用属性。但是整体而言,代码简单和清晰了很多。

 

  其实,该操作还有其他更好的优化方式可以实现,这里只是借这个例子来说明两点:

  1、写代码的时候不要因为前人怎么写,你也去模仿怎么写,这样会让你变得思维方式固化,一定要有新意,养成这种思维的习惯,并且合理地去实现它。

  2、善于从不同的事物中去发现共同点,并将这个共同点抽象出来,从而达到代码复用的目的