阅读目录:

  • 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. js的并行加载以及顺序执行

    重新温习了下这段内容,发现各个浏览器的兼容性真的是搞大了头,处理起来很是麻烦. 现在现总结下并行加载多个js的方法: 1,对于动态createElement('script')的方式,对所有浏览器都是 ...

  2. 负margin的原理以及应用

    负margin在布局中往往起到意想不到的效果,比如在多栏等高布局中就是用该技巧. 虽说网络上关于负margin的实践有很多,但对margin负值为什么会出现这样的效果却没有多少讲解,本篇的目的就是阐述 ...

  3. KVM的前世今生

    1.虚拟化技术的演变过程:软件模拟.虚拟化层翻译.容器虚拟化三个阶段 (1)软件模拟的技术方式 软件模拟是通过软件完全模拟CPU.网卡.芯片组.磁盘等计算机硬件,因为是软件模拟,所以理论上可以模拟任何 ...

  4. C# 将内容写入txt文档

    <1>  FileStream fs = new FileStream(@"D:\text.txt", FileMode.Append); StreamWriter s ...

  5. Service组件简介

    Service是一个应用程序组件,没有图形化界面,通常用来处理一些耗时较长的操作,可以用Service更新ContentProvider,发送Intent以及启动系统的通知等等.Service并不是一 ...

  6. Go项目的目录结构

    项目目录结构如何组织,一般语言都是没有规定.但Go语言这方面做了规定,这样可以保持一致性,做到统一.规则化比较明确. 1.一般的,一个Go项目在GOPATH下,会有如下三个目录: |--bin |-- ...

  7. CCF——Z字形扫描问题

    试题编号: 201412-2 试题名称: Z字形扫描 时间限制: 2.0s 内存限制: 256.0MB 问题描述: 问题描述 在图像编码的算法中,需要将一个给定的方形矩阵进行Z字形扫描(Zigzag ...

  8. BizTalk动手实验(十七)ODBC适配器使用

    更多内容请查看:BizTalk动手实验系列目录                       BizTalk 开发系列 1 课程简介 通过本课程熟悉ODBC适配器的的使用,本练习采用BizTalk 20 ...

  9. C#~异步编程续~.net4.5主推的await&async应用

    返回目录 之前写过两篇关于异步编程的文章,详细可以进入C#~异步编程和C#~异步编程在项目中的使用 .net的各个版本都有自己主推的技术,像.NET1.1中的委托,.NET2.0中的泛型,.NET3. ...

  10. 编译可供C#调用的C/C++动态链接库dll文件

    编译可供C#调用的C/C++动态链接库dll文件,C语言控制台应用程序,探索生成dll过程 由于项目需求,需要公司另一个团队提供相关算法支持,是用C语言编译好的dll库提供给我们进行调用. 但是拿到d ...