.NET重构(类型码的设计、重构方法)
阅读目录:
- 1.开篇介绍
- 2.不影响对象中的逻辑行为(枚举、常量、Entity子类来替代类型码)
- 3.影响对象中的逻辑行为(抽象出类型码,使用多态解决)
- 4.无法直接抽象出类型码(使用策略模式解决)
1】开篇介绍
说到类型码,我们都会很有印象,在某个Entity内部多多少少会出现一两个类型码来表示当前Entity在某个抽象角度属于哪一种层面,比如在EmployeeEntity中,基本上会有一个表示性别的Sex的属性,同时Sex属性的最终保存是在某个sex字段中的,它就是很典型的类型码元素;Sex类型码属性用来表达了在用性别这一个抽象角度对实体进行分类时,那么实体会存在着两种被归纳的层面(男、女);
在这个Sex类型码属性被使用到的任何一个逻辑的地方都会有可能因为它的值不同而进行不同的逻辑分支,就好比我们在EmployeeCollectionEntity对象中定义一个方法,用来返回指定类型的所有EmployeeEntity,我们简单假设在EmployeeeCollectionEntity的内部肯定有一块逻辑是用来根据当前方法的参数进行判断,然后调用不同的方法返回当前集合中的所有执行参数的EmployeeEntity;
上述只是一个简单的使用场景,但是足以能简单说明类型码的意义和使用场景,下面我们将针对上面提到的这一个简单的例子进行三种类型码的使用分析和如何重构设计;在类型码不被任何逻辑使用只是提供给外部一个简单的标识时,我们如何处理;在类型码会直接影响实体内部行为逻辑的情况下,我们如何处理;在类型码会影响实体内部逻辑的时候,但是我们又无法将其直接提取抽象出来时,我们如何处理;
我们带着这个三个简单的问题进行下面的具体分析;
2】不影响对象中的逻辑行为(枚举、常量、Entity子类来替代类型码)
在不影响对象内部逻辑的情况下,问题很好处理;既然不影响对象内部逻辑,那么它的表现形式起码对于实体内部逻辑来说无关紧要;这个时候我们对它的设计可以遵循一个原则就是OO,如果我们使用的是一个简单的数字来表示类型码的状态,那么我们就可以通过三个方式对它进行设计或者重构;
这里有一个小小问题的就是,如果我们正在进行一项局部DomainModel内部的重构时,我们的工作量会很大而且需要很好的单元测试来支撑;但是如果我们目前正在设计一个Entity问题就很简单;
下面我们用上面1】节提到的简单场景作为本节演示示例的领域模型;
EmployeeEntity 代码:
public class EmployeeEntity
{
private int sex; public int Sex
{
get { return sex; }
set { sex = value; }
}
}
EmployeeCollectionEntity代码:
public class EmployeeCollectionEntity : List<EmployeeEntity>
{
public IEnumerable<EmployeeEntity> GetEntityBySex(int sex)
{
return from item in this where item.Sex== sex select item;
}
}
测试代码,为了方便起见,我就没有特地创建UnitTests项目,而是简单的使用控制台程序模拟:
EmployeeCollectionEntity empCollection = new EmployeeCollectionEntity()
{
new EmployeeEntity() { Sex= },
new EmployeeEntity() { Sex = },
new EmployeeEntity() { Sex = }
}; var resultList = empCollection.GetEntityBySex();
if (resultList.Count() == && resultList.ToList()[].Sex== && resultList.ToList()[].Sex==)
Console.WriteLine("is ok"); Console.ReadLine();
上述代码很简单,一个Employee用来表示员工实体,EmployeeCollectionEntity表示员工实体集,用来封装一组包含业务逻辑的Empoyee集合;目前在EmployeeCollectionEntity中有一个方法GetEntityBySex(int sex),用来根据性别类型码来获取集合内部中满足条件的所有EmpoyeeEntity,在单元测试中的代码,我们使用1表示女性,2表示男性,单元测试通过测试代码正确的查询出两组男性EmployeeEntity实体;
下面我们将逐步使用三种方式对这种类型的业务场景进行重新设计也可以称为重构;
第一:使用枚举类型替换类型码数字;
EmployeeEntity代码:
public class EmployeeEntity
{
public enum EmployeeSex
{
Male,
Female
} private EmployeeSex sex; public EmployeeSex Sex
{
get { return sex; }
set { sex= value; }
}
}
EmployeeCollectionEntity代码:
public class EmployeeCollectionEntity : List<EmployeeEntity>
{
public IEnumerable<EmployeeEntity> GetEntityBySex(EmployeeEntity.EmployeeSex sex)
{
return from item in this where item.Sex== sexselect item;
}
}
测试代码:
EmployeeCollectionEntity empCollection = new EmployeeCollectionEntity()
{
new EmployeeEntity() { Sex= EmployeeEntity.EmployeeSex.Female },
new EmployeeEntity() { Sex= EmployeeEntity.EmployeeSex.Male },
new EmployeeEntity() { Sex= EmployeeEntity.EmployeeSex.Male }
}; var resultList = empCollection.GetEntityBySex(EmployeeEntity.EmployeeSex.Male);
if (resultList.Count() == && resultList.ToList()[].Sex== EmployeeEntity.EmployeeSex.Male &&
resultList.ToList()[].Sex== EmployeeEntity.EmployeeSex.Male)
Console.WriteLine("is ok"); Console.ReadLine();
通过使用枚举我们能很好的使用OOD的好处,这样代码中不会到处充斥这乱七八糟的魔幻数字;
第二:使用常量来代替类型码;
其实使用常量来代替类型码时,比较常见的业务场景是在和远程交互的时候,因为在我们将Entity翻译成某种传输对象的时候需要将它的属性使用字符串的形式表达;比如这里的EmployeeEntity,假设我们需要将某一个EmployeeEntity发送到某个消息队列,然后消息队列的后端程序需要将它直接插入到数据库中,这个时候,我们的DomainModel在消息队列的后端程序中是不存在的,也就是说并没有和数据库映射过,这里的属性类型码将是和数据库等价的字符串;所以如果我们在选择使用枚举还是常量来替代类型码是,选择的标准就是类型码是否需要持久化,也就是字符串化;
EmployeeEntity代码:
public class EmployeeEntity
{
public const int Male = ;
public const int Female = ; private int sex; public int Sex
{
get { return sex; }
set { Sex= value; }
}
}
EmployeeCollectionEntity代码:
public IEnumerable<EmployeeEntity> GetEntityBySex(int sex)
{
return from item in this where item.Sex== sexselect item;
}
测试代码:
EmployeeCollectionEntity empCollection = new EmployeeCollectionEntity()
{
new EmployeeEntity() { Sex= EmployeeEntity.Female},
new EmployeeEntity() { Sex= EmployeeEntity.Male },
new EmployeeEntity() { Sex= EmployeeEntity.Male}
}; var resultList = empCollection.GetEntityBySex(EmployeeEntity.Male);
if (resultList.Count() == && resultList.ToList()[].Sex== EmployeeEntity.Male &&
resultList.ToList()[].Sex == EmployeeEntity.Male)
Console.WriteLine("is ok"); Console.ReadLine();
使用常量来代替类型码就是在接口上只能使用数字来表示IEnumerable<EmployeeEntity> GetEntityBySex(int sex),然后我们在调用的时候会直接使用常量类型empCollection.GetEntityBySex(EmployeeEntity.Male);
第三:使用Entity子类来替代类型码;
对于EmployeeEntity如果在Sex角度上存在继承体系,那么我们就可以使用Entity子类的方式来解决;现假设,对于性别为男和女都分别从EmployeeEntity上继承各自的体系,MaleEmployeeEntity为男,FemaleEmployeeEntity为女,当然真实场景中不会为了这一个小小的性别就独立出一个继承体系;
EmployeeEntity代码:
public abstract class EmployeeEntity
{
public abstract bool IsFemale { get; }
public abstract bool IsMale { get; }
}
这个时候EmployeeEntity已经不在是一个真实的Employee了;
MaleEmployeeEntity代码:
public class MaleEmployeeEntity : EmployeeEntity
{
public override bool IsFemale
{
get { return false; }
}
public override bool IsMale
{
get { return true; }
}
}
FemaleEmployeeEntity代码:
public class FemaleEmployeeEntity : EmployeeEntity
{
public override bool IsFemale
{
get { return true; }
}
public override bool IsMale
{
get { return false; }
}
}
EmployeeCollectionEntity代码:
public class EmployeeCollectionEntity : List<EmployeeEntity>
{
public IEnumerable<EmployeeEntity> FemaleEmployeeList
{
get
{
return from item in this where item.IsFemale select item;
}
} public IEnumerable<EmployeeEntity> MaleEmployeeList
{
get
{
return from item in this where item.IsMale select item;
}
}
}
测试代码:
EmployeeCollectionEntity empCollection = new EmployeeCollectionEntity()
{
new FemaleEmployeeEntity(),
new MaleEmployeeEntity() ,
new MaleEmployeeEntity()
}; var resultList = empCollection.MaleEmployeeList;
if (resultList.Count() == && resultList.ToList()[].IsMale && resultList.ToList()[].IsMale)
Console.WriteLine("is ok"); Console.ReadLine();
既然咱们不存在类型码了,那么就不会存在根据参数来获取数据的接口,所以我们稍微变换一下,将参数拆成具体的属性用来直接返回数据集合;
3】影响对象中的逻辑行为(抽象出类型码,使用多态解决)
上面2】节中讲到的方式都是类型码不影响程序具体业务逻辑的情况下的设计方式,但是一旦当类型码直接影响到我们DomainModel中的具体业务逻辑的情况下我就需要将类型码进行提取并抽象出继承体系,然后将具体的逻辑跟类型码继承体系走,这也是面向对象中的面向职责设计,将行为尽可能的放入它调用最平凡的对象中去;
现在假设EmployeeEntity中有一组订单OrderCollection,现在要根据EmployeeEntity的不同级别EmployeeLevel获取(GetDistributionOrders)需要配送的OrderCollection,这里有一个业务规则就是不同的等级在每次获取配送订单的时候是有不同的条件限制的,具体的条件限制跟当前的EmployeeLevel有关系,那么这个时候我们就需要将跟level相关的逻辑封装进EmployeeLevel中去;
图1:

Order代码:
public class Order
{
public DateTime SubmitDtime { get; set; }
}
OrderCollection代码:
public class OrderCollection : List<Order>
{ }
EmployeeEntity代码:
public class EmployeeEntity
{
public EmployeeLevel Level { get; set; } public OrderCollection AllDistributeionOrders { get; set; } public OrderCollection GetDistributionOrders()
{
return Level.GetDistributionOrders();//将逻辑推入到类型码之后的调用方式;
}
}
EmployeeLevel代码:
public abstract class EmployeeLevel
{
public EmployeeEntity employee;
public abstract OrderCollection GetDistributionOrders();
} public class Normal : EmployeeLevel
{
public override OrderCollection GetDistributionOrders()
{
if (employee.AllDistributeionOrders == null && employee.AllDistributeionOrders.Count == ) return null;
var orders = from order in employee.AllDistributeionOrders
where order.SubmitDtime <= DateTime.Now.AddDays(-)//Normal 推迟五天配送
select order; if (orders.ToList().Count == ) return null; OrderCollection result = new OrderCollection(); orders.ToList().ForEach(order => { result.Add(order); }); return result; }
} public class Super : EmployeeLevel
{
public override OrderCollection GetDistributionOrders()
{
if (employee.AllDistributeionOrders == null && employee.AllDistributeionOrders.Count == ) return null;
var orders = from order in employee.AllDistributeionOrders
where order.SubmitDtime <= DateTime.Now.AddDays(-)//Super 推迟一天配送
select order; if (orders.ToList().Count == ) return null; OrderCollection result = new OrderCollection(); orders.ToList().ForEach(order => { result.Add(order); }); return result;
}
}
测试代码:
OrderCollection orderColl = new OrderCollection();
orderColl.Add(new Order() { SubmitDtime = DateTime.Now.AddDays(-) });
orderColl.Add(new Order() { SubmitDtime = DateTime.Now.AddDays(-) });
EmployeeEntity employee = new EmployeeEntity()
{
AllDistributeionOrders = orderColl
}; EmployeeLevel level = new Super() { employee = employee };
employee.Level = level; var result = employee.GetDistributionOrders();
if (result.Count == )
Console.WriteLine("Is ok"); Console.ReadLine();
我们定义了两个EmployeeLevel,一个是Normal的,也就是普通的,他的配送限制条件是:配送必须推迟五天;二个Super,也就是超级的,他的配送只推迟一天;这样的逻辑分支,如果我们没有将类型码抽象出来进行设计,那么我们将面临着一个条件分支的判断,当后面需要加入其他Level的时候我们就会慢慢的陷入到判断分支的泥潭;
4】无法直接抽象出类型码(使用策略模式解决)
在3】节中,我们能很好的将类型码抽象出来,但是如果我们面临着一个重构项目时,我们很难去直接修改大面积的代码,只能平衡一下将类型码设计成具有策略意义的方式,不同的类型码对应着不同的策略方案;
我们还是拿3】节中的示例来说,现在假设我们在重构一个直接使用int作为类型码的EmployeeEntity,那么我们不可能去直接修改EmployeeEntity内部的逻辑,而是要通过引入策略工厂将不同的类型码映射到策略方法中;
图2:

由于该节代码比较简单,所以就不提供示例代码,根据上面的UML类图基本上可以知道代码结构;
.NET重构(类型码的设计、重构方法)的更多相关文章
- 重构学习day01 类型码 类型码的上层建筑 与类型码相关的重构方法 1.使用子类代替类型码 2.使用状态或策略模式代替类型码
名词:类型码 类型码的上层建筑 重构方法 1.使用子类代替类型码 2.使用状态/策略模式代替类型码 类中存在方法把某个字段当作条件,根据字段值的不同,进行不同的处理.(自定义概念)则这个字段叫做:类型 ...
- 由学习《软件设计重构》所想到的代码review(一)
前言 对于一个程序猿来讲怎样来最直接的来衡量他的技术能力和产出呢?我想最直观的作法是看他的代码编写能力,就拿我常常接触的一些程序猿来看,他们买了非常多技术重构类书籍.可是看完后代码编写能力并没有显著提 ...
- 设计一个方法injectBeforeAsyncSend,能够实现如下功能:在发起异步请求之前打印出请求的类型、URL、method、body、timestamp 等信息。
异步请求逻辑注入 工作中我们需要对异步请求的请求信息打印日志,但是又不能耦合在业务代码中打印.请设计一个方法injectBeforeAsyncSend,能够实现如下功能:在发起异步请求之前打印出请求的 ...
- html2canvas实现浏览器截图的原理(包含源码分析的通用方法)
DevUI是一支兼具设计视角和工程视角的团队,服务于华为云DevCloud平台和华为内部数个中后台系统,服务于设计师和前端工程师. 官方网站:devui.design Ng组件库:ng-devui(欢 ...
- 【重构】AndroidStudio中代码重构菜单Refactor功能详解
代码重构几乎是每个程序员在软件开发中必须要不断去做的事情,以此来不断提高代码的质量.Android Stido(以下简称AS)以其强大的功能,成为当下Android开发工程师最受欢迎的开发工具,也是A ...
- [从源码学设计]蚂蚁金服SOFARegistry之程序基本架构
[从源码学设计]蚂蚁金服SOFARegistry之程序基本架构 0x00 摘要 之前我们通过三篇文章初步分析了 MetaServer 的基本架构,MetaServer 这三篇文章为我们接下来的工作做了 ...
- [从源码学设计]蚂蚁金服SOFARegistry之网络封装和操作
[从源码学设计]蚂蚁金服SOFARegistry之网络封装和操作 目录 [从源码学设计]蚂蚁金服SOFARegistry之网络封装和操作 0x00 摘要 0x01 业务领域 1.1 SOFARegis ...
- [从源码学设计]蚂蚁金服SOFARegistry网络操作之连接管理
[从源码学设计]蚂蚁金服SOFARegistry网络操作之连接管理 目录 [从源码学设计]蚂蚁金服SOFARegistry网络操作之连接管理 0x00 摘要 0x01 业务领域 1.1 应用场景 0x ...
- [从源码学设计]蚂蚁金服SOFARegistry之消息总线
[从源码学设计]蚂蚁金服SOFARegistry之消息总线 目录 [从源码学设计]蚂蚁金服SOFARegistry之消息总线 0x00 摘要 0x01 相关概念 1.1 事件驱动模型 1.1.1 概念 ...
随机推荐
- 一种C#生成符合Java规则的二进制文件方法
文章版权由作者李晓晖和博客园共有,若转载请于明显处标明出处:http://www.cnblogs.com/naaoveGIS/. 1.一个项目中的真实问题 实际项目中,本想通过C#制作小工具生成SHP ...
- 【集合框架】JDK1.8源码分析之IdentityHashMap(四)
一.前言 前面已经分析了HashMap与LinkedHashMap,现在我们来分析不太常用的IdentityHashMap,从它的名字上也可以看出来用于表示唯一的HashMap,仔细分析了其源码,发现 ...
- Android之assets资源
assets目录下存放的原生资源文件,通过getAssets()方法获取. 使用: InputStream inputStream; try { inputStream = getAssets().o ...
- PHP配置限制文件大小上传
修改PHP上传文件大小限制的方法1. 一般的文件上传,除非文件很小.就像一个5M的文件,很可能要超过一分钟才能上传完.但在php中,默认的该页最久执行时间为 30 秒.就是说超过30秒,该脚本就停止执 ...
- vue-lazy-render: 延迟渲染大组件,增强页面切换流畅度
最近用element来做项目,在开发的过程中,突然发现页面的操作和切换在数据量大的时候相当卡,后来提了个issue,在furybean解答后才知道,我每个单元格都加了tooltip,会生成大量的节点, ...
- 《Head First 设计模式》之观察者模式
作者:Grey 原文地址:http://www.cnblogs.com/greyzeng/p/5918205.html 模式名称 观察者模式(Observer Pattern) 需求 我们要通过Wea ...
- SQL Server时间粒度系列----第8节位运算以及设置日历数据表节假日标志详解
本文目录列表: 1.位运算 2.设置日历数据表节假日标志 3.总结语 4.参考清单列表 位运算 SQL Server支持的按位运算符有三个,分别为:按位与(&).按位或(|).按位异或 ...
- “WPF老矣,尚能饭否”—且说说WPF今生未来(上):担心
近日微软公布了最新的WPF路线图,一片热议:对于老牌控件提供商葡萄城来说,这是WPF系列控件一个重要的机遇,因此,Spread Studio for WPF产品做了一次重要更新,并随着Spread S ...
- ActiveX(三)ActiveX 调用 Js
在上一篇随笔: ActiveX(二)Js 监听 ActiveX中的事件 中,已经可以实现 Js 监听 ActiveX中的事件,至此.Js 和 ActiveX 已经可以实现双向通讯了.但是.这样的实现 ...
- 可控制导航下拉方向的jQuery下拉菜单代码
效果:http://hovertree.com/texiao/nav/1/ 代码如下: <!DOCTYPE html> <html> <head> <meta ...