前两篇(思路和方法、重构计划)从大的方面上谈了关于重构的话题,这次从小的代码上来看。我们来看下一个的代码如何从简单到复杂,然后重构这些代码。
单个对象复制
在初步的需求中有个很简单的业务,就是定义销售合同,并且合同中可以配置产品设备数据,如下:
其中有个业务功能就是需要对已经存在的销售合同进行复制、剪贴和粘贴的工作。
对于程序来说,它其实就需要实现IClone接口就可以了,
/// 复制
/// </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.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<string, object> mContextParams;
/// <summary>
/// 等待被复制的对象
/// </summary>
private List<object> mWaitToCopiedItems = new List<object>();
/// <summary>
/// 被复制对象-新对象的索引关系
/// </summary>
private Dictionary<object, object> mItemIndexs = new Dictionary<object, object>();
/// <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<string, object>();
}
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.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<string, object> contextParams, Dictionary<object, object> mItemIndexs);
}
}
3、用于该复制操作的自定义属性KeepFixedValueAttribute
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Kevin.CommonFunction.Clone
{
/// <summary>
/// 当在CopyContainer中执行复制的时候,保留对象特定字段的初始值
/// </summary>
public class KeepFixedValueAttribute : Attribute
{
}
}
上述是所有的实现代码,下面看下是如何使用的:
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<string, object> contextParams, Dictionary<object, object> 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、善于从不同的事物中去发现共同点,并将这个共同点抽象出来,从而达到代码复用的目的