前两篇(思路和方法、重构计划)从大的方面上谈了关于重构的话题,这次从小的代码上来看。我们来看下一个的代码如何从简单到复杂,然后重构这些代码。
单个对象复制
在初步的需求中有个很简单的业务,就是定义销售合同,并且合同中可以配置产品设备数据,如下:
其中有个业务功能就是需要对已经存在的销售合同进行复制、剪贴和粘贴的工作。
对于程序来说,它其实就需要实现IClone接口就可以了,
代码
//复制///publicobjectClone()
{
TestObj copiedObj=newTestObj();
copiedObj.Parent=null;
copiedObj.Name=this.Name;foreach(TestObj objinthis.Child)
{
TestObj childCopiedObj=obj.Clone()asTestObj;
childCopiedObj.Parent=copiedObj;
copiedObj.Child.Add(childCopiedObj);
}returncopiedObj;
}
OK,这很好的实现了业务需求。
批量复制不同对象
业务需求发生了变化,现在对于前期简单的销售合同进行了完善,同时让它变得异常复杂,如下图,
现在要求在所有内部对象都支持复制、剪贴和粘贴,并且也要支持每个部分的数据可以独立导出为特殊的文件,作为数据模板,对于其他合同为了简化操作可以将该模块导入,这些动作在实现前都必须要执行复制,前面的动作不用说都可以理解,对于后面的导入和导出来说,虽然主要通过序列化来支持,但是由于业务特殊性也必须先做复制操作,然后需要对复制后的数据做特殊处理,然后才去序列化。
对于这次的需求来说,很多人的实现方式是对每个类都模仿Clone的方式来写自己的复制方法,这种方式一定是正确的,并且性能是最好的,不过它也是代码量最多的,同时随着业务的进一步完善,它的代码会越来越多。
按照上述的方式来实现,可能需要两周左右的时间,可以想想有没有办法减少开发时间呢?而且也让以后的开发时间减少?
想了很久,终于有了一种思路:
具体的实现代码看下这三个类的代码,
1、最核心的类CopyContainer
代码
usingSystem;usingSystem.Collections.Generic;usingSystem.Linq;usingSystem.Text;usingSystem.Reflection;namespaceKevin.CommonFunction.Clone
{//复制元素的容器/执行批量的复制,默认只复制对象的值类型,引用关系保留///publicclassCopyContainer
{#region属性//上下文环境参数///privateDictionarymContextParams;//等待被复制的对象///privateListmWaitToCopiedItems=newList();//被复制对象-新对象的索引关系///privateDictionarymItemIndexs=newDictionary();//构造函数的缓存///privateDictionarymConstcCache=newDictionary();//字段的缓存///privateDictionary>mFieldInfoCache=newDictionary>();//复制后的新对象///privateListmNewItems=newList();#endregion#region构造函数/publicCopyContainer()
{
}#endregion#region方法//添加等待被复制的对象//需要复制的对象publicvoidAddToWaitCopiedItems(objectwaitCopiedItem)
{if(waitCopiedItem!=null&&!mWaitToCopiedItems.Contains(waitCopiedItem))
{this.mWaitToCopiedItems.Add(waitCopiedItem);
}
}//添加环境参数//环境中的唯一主键///环境参数publicvoidAddToContextParams(stringkey,objectcontextParam)
{if(mContextParams==null)
{
mContextParams=newDictionary();
}this.mContextParams.Add(key, contextParam);
}//复制对象//所有复制后的对象列表publicListClone()
{if(this.mWaitToCopiedItems==null)
{returnnull;
}foreach(objectobjinthis.mWaitToCopiedItems)
{objectnewObj=CopyToNewObj(obj);
mItemIndexs.Add(obj, newObj);
mNewItems.Add(newObj);
}foreach(objectnewObjinmNewItems)
{
ICopyReference copiedObj=newObjasICopyReference;if(copiedObj!=null)
{
copiedObj.SetReference(this.mContextParams,this.mItemIndexs);
}
}returnthis.mNewItems;
}//复制对象//被复制的对象///复制后的对象privateobjectCopyToNewObj(objectcopeidItem)
{
Type copiedType=copeidItem.GetType();
ConstructorInfo constc=this.GetConstructor(copiedType);
Listfields=this.GetFieldInfos(copiedType);objectnewObj=constc.Invoke(null);foreach(FieldInfo fieldinfields)
{objectcopiedValue=field.GetValue(copeidItem);
field.SetValue(newObj, copiedValue);
}returnnewObj;
}//获取构造函数//被复制对象的类型///构造函数privateConstructorInfo 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 constcinconstructors)
{
ParameterInfo[] paramsInConst=constc.GetParameters();if(paramsInConst.Length==0)
{
copiedConstc=constc;break;
}
}if(copiedConstc==null)
{thrownewException(string.Format("类 '{0}' 缺少无参的构造函数", copiedType.FullName));
}this.mConstcCache.Add(copiedType, copiedConstc);
}returncopiedConstc;
}//获取字段//被复制对象的字段///字段privateListGetFieldInfos(Type copiedType)
{
Listfields=null;if(this.mFieldInfoCache.ContainsKey(copiedType))
{
fields=this.mFieldInfoCache[copiedType];
}//该字段未加入缓存else{
fields=newList();
FieldInfo[] fieldsInCT=copiedType.GetFields(BindingFlags.Public|BindingFlags.NonPublic|BindingFlags.Instance);foreach(FieldInfo fieldIteminfieldsInCT)
{//过滤带有KeepFixedValueAttribute自定义属性的字段if(fieldItem.GetCustomAttributes(typeof(KeepFixedValueAttribute),true).Length==0)
{
fields.Add(fieldItem);
}
}this.mFieldInfoCache.Add(copiedType, fields);
}returnfields;
}#endregion}
}
2、复制对象引用的操作接口ICopyReference
代码
usingSystem;usingSystem.Collections.Generic;usingSystem.Linq;usingSystem.Text;namespaceKevin.CommonFunction
{//复制对象引用的操作接口/当在CopyContainer中执行复制的时候,对于复制对象引用的支持//publicinterfaceICopyReference
{//设置复制对象的引用关系//上下文环境参数///被复制对象-新对象的索引关系voidSetReference(DictionarycontextParams, DictionarymItemIndexs);
}
}
3、用于该复制操作的自定义属性KeepFixedValueAttribute
代码
usingSystem;usingSystem.Collections.Generic;usingSystem.Linq;usingSystem.Text;namespaceKevin.CommonFunction.Clone
{//当在CopyContainer中执行复制的时候,保留对象特定字段的初始值///publicclassKeepFixedValueAttribute : Attribute
{
}
}
上述是所有的实现代码,下面看下是如何使用的:
代码
usingSystem;usingSystem.Text;usingSystem.Collections.Generic;usingSystem.Linq;usingMicrosoft.VisualStudio.TestTools.UnitTesting;usingKevin.CommonFunction.Clone;usingKevin.CommonFunction;namespaceKevin.NUnitTest.Clone
{
[TestClass]publicclassT_CopyContainer
{
[TestMethod]publicvoidClone()
{
CopyContainer cc=newCopyContainer();
TestObj oldObj=newTestObj();
cc.AddToWaitCopiedItems(oldObj);
ListnewItems=cc.Clone();
Assert.AreEqual(newItems.Count,2);
}
}publicclassTestObj : ICopyReference
{//自动生成的主键///publicintmKey=System.Guid.NewGuid().GetHashCode();//上级对象///publicTestObj Parent;//下级对象列表///publicListChild=newList();//名称///publicstringName;//设置复制对象的引用关系//上下文环境参数///被复制对象-新对象的索引关系publicvoidSetReference(DictionarycontextParams, DictionarymItemIndexs)
{
ListarrCopiedItems=newList();foreach(TestObj objinthis.Child)
{if(mItemIndexs.ContainsKey(obj))
{
arrCopiedItems.Add(mItemIndexs[obj]);
}else{
arrCopiedItems.Add(obj);
}
}
}
}
}
虽然,每个支持复制操作的对象都需要实现ICopyReference接口,这是为了处理对象内的引用属性。但是整体而言,代码简单和清晰了很多。
其实,该操作还有其他更好的优化方式可以实现,这里只是借这个例子来说明两点:
1、写代码的时候不要因为前人怎么写,你也去模仿怎么写,这样会让你变得思维方式固化,一定要有新意,养成这种思维的习惯,并且合理地去实现它。
2、善于从不同的事物中去发现共同点,并将这个共同点抽象出来,从而达到代码复用的目的