【讨论二】服务层(Service)的功能参数列表的粒度
2011-06-12 13:21 bugfly 阅读(1683) 评论(11) 编辑 收藏 举报我们一般在设计服务层的时候总会有这种情况,究竟参数列表是简单类型还是对象类型?
首先我们从比较常见的场景入手,修改订单。这里我们先不考虑它是否应该归属于领域对象。
根据场景很自然就能提炼出一个功能:EditOrder(XXX),我们开始看看参数列表的写法的种类。我大概想到3种。
1.void EditOrder(string orderName,string orderText,float Money);
2.void EditOrder(Order order);
3.void EditOrder(OrderDTO dto);
先看看我对这三种设计的见解。
第一种:很直观,也是最早被我们使用的方式,但这种设计有很多弊端。
1)容易造成参数组合爆炸,因为对象的属性有很多,一个参数组合列表不定,两个参数组合列表不定,三个四个组合列表等等若此类推,理论上有人脑处理不能的组合,也就是很难适应变化,你只能不断地加入重载功能到服务层接口里面,并且会出现很多类似的代码段,果断后期悲剧。
伪代码实现:
Order Model
class Order
{
public int id{get;set;}
public string Name{get;set;}public string Number{get;set;}public string Text{get;set;}
public float Money{get;set;}
public DateTime CreateDate{get;set;}
}
按照第一种思路具体对Service功能的实现
void EditOrder(int id,string orderName,string orderText,float Money)
{
Order order=this._OrderRepository.find(id);
order.Name=orderName;
order.Text=orderText;
order.Money=Money;
this._OrderRepository.save(order);
}
2)使用简单类型作为参数列表,在一定数量级的情况下,会出现重载冲突,如
void EditOrder(int id,string orderName,string orderText,float Money);
void EditOrder(int id,string orderNumber,string orderTitle,float Money);
显然这个重载方法会有编译错误,为了迎合这种需求,你只能通过修改方法名字来满足,十分别扭。以下是修改了的代码
void EditOrder(int id,string orderName,string orderText,float Money)
{
Order order=this._OrderRepository.find(id);
order.Name=orderName;
order.Text=orderText;
order.Money=Money;
this._OrderRepository.save(order);
}
void EditOrderNum(int id,string orderNumber,string orderText,float Money)
{
Order order=this._OrderRepository.find(id);
order.Number=orderNumber; //把Name改成Number属性修改
order.Text=orderText;
order.Money=Money;
this._OrderRepository.save(order);
}
从这个修改结果可以看出,存在很多重复代码段,都是类似代码,即不能复用,也不能不写,而且当需求不断变化,这个服务层就越来越厚,也是上述文章提到的弊端之一。
第二种:直接使用领域对象作为数据载体,好处是能够适应所有围绕领域模型的需求变化,解决了第一种情况不断重载的局面,也随理成章地解决了重载冲突的局面。然而这里存在一些弊端。
1)由于数据载体是一个领域对象,调用端传送回来的数据可能只有几个属性,也就是很可能存在大量的null值和默认值的属性,面对这种情况,我们要在功能实现里面对对象属性进行筛选验证,当然只有几个属性这不能当一回事,但领域对象大起上来,有几十个属性或以上,这部分工作就变得痛苦起来了。
按照第二种思路对Service功能的实现
void EditOrder(Order data)
{
if(data.id!=0)
{
Order order=this._OrderRepository.find(data.id);
if(!string.IsNullOrEmpty(data.Name))
{
order.Name=data.Name;
}
if(data.CreateDate!=new DateTime())
{
order.CreateDate=data.CreateDate;
}
this._OrderRepository.save(order);
}
throw new Exception("Error");
}
要注意的是,这里的Order data参数,只充当一个数据载体,而不是一个领域对象。
第三种:使用DTO代替领域对象作为数据载体,其实我也用过这种做法,但我找不到其好处,引用别人的观点就是,隔离了领域对象对UI的影响,安全性得到保证,从我的实践经验来看,确实是把领域对象隔离出UI,但换来的是大量的DTO管理和适配问题,这些问题比维护问题还头痛,如果是单纯从分布式的角度来解决问题,结合第二种和第三种做法可以产生一变种,前端返回给后台的数据载体用领域对象,而前端接收的数据载体用DTO。
结论:第二种做法是暂时面对非分布式开发是下比较好的做法,但总感觉有点别扭,所以把问题拿出来,集思广益!希望大家积极参与!我相信你们的经验中一定有更好的解决办法。
作者:桀骜的灵魂
出处:http://www.cnblogs.com/HuntSoul/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。