阅读目录:

  • 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类图基本上可以知道代码结构;

作者:王清培

出处:http://www.cnblogs.com/wangiqngpei557/

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面

.NET重构(类型码的设计、重构方法)的更多相关文章

  1. 重构学习day01 类型码 类型码的上层建筑 与类型码相关的重构方法 1.使用子类代替类型码 2.使用状态或策略模式代替类型码

    名词:类型码 类型码的上层建筑 重构方法 1.使用子类代替类型码 2.使用状态/策略模式代替类型码 类中存在方法把某个字段当作条件,根据字段值的不同,进行不同的处理.(自定义概念)则这个字段叫做:类型 ...

  2. 由学习《软件设计重构》所想到的代码review(一)

    前言 对于一个程序猿来讲怎样来最直接的来衡量他的技术能力和产出呢?我想最直观的作法是看他的代码编写能力,就拿我常常接触的一些程序猿来看,他们买了非常多技术重构类书籍.可是看完后代码编写能力并没有显著提 ...

  3. 设计一个方法injectBeforeAsyncSend,能够实现如下功能:在发起异步请求之前打印出请求的类型、URL、method、body、timestamp 等信息。

    异步请求逻辑注入 工作中我们需要对异步请求的请求信息打印日志,但是又不能耦合在业务代码中打印.请设计一个方法injectBeforeAsyncSend,能够实现如下功能:在发起异步请求之前打印出请求的 ...

  4. html2canvas实现浏览器截图的原理(包含源码分析的通用方法)

    DevUI是一支兼具设计视角和工程视角的团队,服务于华为云DevCloud平台和华为内部数个中后台系统,服务于设计师和前端工程师. 官方网站:devui.design Ng组件库:ng-devui(欢 ...

  5. 【重构】AndroidStudio中代码重构菜单Refactor功能详解

    代码重构几乎是每个程序员在软件开发中必须要不断去做的事情,以此来不断提高代码的质量.Android Stido(以下简称AS)以其强大的功能,成为当下Android开发工程师最受欢迎的开发工具,也是A ...

  6. [从源码学设计]蚂蚁金服SOFARegistry之程序基本架构

    [从源码学设计]蚂蚁金服SOFARegistry之程序基本架构 0x00 摘要 之前我们通过三篇文章初步分析了 MetaServer 的基本架构,MetaServer 这三篇文章为我们接下来的工作做了 ...

  7. [从源码学设计]蚂蚁金服SOFARegistry之网络封装和操作

    [从源码学设计]蚂蚁金服SOFARegistry之网络封装和操作 目录 [从源码学设计]蚂蚁金服SOFARegistry之网络封装和操作 0x00 摘要 0x01 业务领域 1.1 SOFARegis ...

  8. [从源码学设计]蚂蚁金服SOFARegistry网络操作之连接管理

    [从源码学设计]蚂蚁金服SOFARegistry网络操作之连接管理 目录 [从源码学设计]蚂蚁金服SOFARegistry网络操作之连接管理 0x00 摘要 0x01 业务领域 1.1 应用场景 0x ...

  9. [从源码学设计]蚂蚁金服SOFARegistry之消息总线

    [从源码学设计]蚂蚁金服SOFARegistry之消息总线 目录 [从源码学设计]蚂蚁金服SOFARegistry之消息总线 0x00 摘要 0x01 相关概念 1.1 事件驱动模型 1.1.1 概念 ...

随机推荐

  1. HTML5 视频(二) <video> 使用 DOM 进行控制

    HTML5 <video> 使用 DOM 进行控制 一.HTML5 <video> 元素同样拥有方法.属性和事件. 其中的方法用于播放.暂停以及加载等.其中的属性(比如时长.音 ...

  2. ASP.NET的session操作方法总结

    在开发ASP.NET程序时,需要对相关数据进行缓存,缓存较多的主要是用户的身份信息,现提供几个对session操作较为常用的方法: 1.添加session,对设置对应的时间: /// <summ ...

  3. 基本概念----Beginning Visual C#

    更多相关文章,见本人的个人主页:zhongxiewei.com 变量 注释方式:// 注释在这里和/* 注释在这里 */ 整形变量的类型: Type Alias for Allowed Values ...

  4. 在ASP.NET Core使用Middleware模拟Custom Error Page功能

    一.使用场景 在传统的ASP.NET MVC中,我们可以使用HandleErrorAttribute特性来具体指定如何处理Action抛出的异常.只要某个Action设置了HandleErrorAtt ...

  5. Python解析非标准JSON(Key值非字符串)

    采集数据的时候经常碰到一些JSON数据的Key值不是字符串,这些数据在JavaScript的上下文中是可以解析的,但在Python中,没有该部分数据的上下文,无法采用json.loads(JSON)的 ...

  6. HTML5小游戏之见缝插针

    今天给大家带来的就是一款叫做<见缝插针>的游戏.有空你就往里插,直到你无处可插!看你能过多少关! 简洁大气 黑白搭配游戏画面非常的简洁,米白色的背景中央,放置着一个不断旋转的太阳状的球体, ...

  7. HTML5 Canvas彩色小球碰撞运动特效

    脚本简介 HTML5 Canvas彩色小球碰撞运动特效是一款基于canvas加面向对象制作的运动小球动画特效.   效果展示 http://hovertree.com/texiao/html5/39/ ...

  8. Hibernate实现有两种配置,xml配置与注释配置

    hibernate实现有两种配置,xml配置与注释配置. (1):xml配置:hibernate.cfg.xml (放到src目录下)和实体配置类:xxx.hbm.xml(与实体为同一目录中) < ...

  9. Java动态代理全面分析

    代理模式 解说:给某一个对象提供一个代理,并由代理对象控制对原对象的引用: 代理模式需要以下几个角色: 1  主题:规定代理类和真实对象共同对外暴露的接口: 2  代理类:专门代理真实对象的类: 3 ...

  10. 大公司c#&.net转型java的原因有哪些?

    历来就听说有编程语言“鄙视链”的说法,而如今月经贴上的那些事儿,还真让我给遇到了. 以下内容来自知乎,纯属扯淡,易引发口水战,看完勿人身攻击. 目的给盲目的公司决策者.开发人员科普下,有个客观清醒的认 ...