我们可以将异常处理和事务管理合并成一个服务,如下:
public interface ITransactionManager2
{
void Wrapper(Action method);
}
public class TransactionManager2 : ITransactionManager2
{
public void Wrapper(Action method)
{
using (var ts=new TransactionScope())
{
var retires = 3;
var succeeded = false;
while (!succeeded)
{
try
{
method();
ts.Complete();
succeeded = true;
}
catch (Exception ex)
{
if (retires >= 0)
retires--;
else
{
if (!ExceptionHelper.Handle(ex))
throw;
}
}
}
}
}
}
处理注入依赖过多的另一种方法是将所有的服务移到一个聚合服务或者门面服务(即,使用门面模式将所有的小服务组合成一个服务来组织这些小服务),我们这个例子中,TransactionManager 和ExceptionHandler 服务是独立的,但是可以使用第三个门面类来组织它们的使用。
门面模式 The Facade Pattern
门面模式为更大的或者更复杂的代码段提供了一个简化接口,比如,一个提供了许多方法和选项的服务类可以放到一个门面接口中,这样就可以通过限制选项或者提供简化方法的子集来降低复杂度。
public interface ITransactionFacade
{
void Wrapper(Action method);
}
public class TransactionFacade : ITransactionFacade
{
private readonly ITransactionManager _transactionManager;
private readonly IExceptionHandler _exceptionHandler;
public TransactionFacade(ITransactionManager transactionManager, IExceptionHandler exceptionHandler)
{
_transactionManager = transactionManager;
_exceptionHandler = exceptionHandler;
}
public void Wrapper(Action method)
{
_exceptionHandler.Wrapper(()=>
_transactionManager.Wrapper(method)
);
}
}
这样修改后,Accrual 和Redemption 服务方法中的Wrapper样板代码就减少了很多,更干净了。但是还存在防御编程和logging的问题。
使用装饰器模式重构
不使用AOP重构代码的另一种方式是使用装饰器模式或代理器模式。剧透一下:装饰器/代理器模式只是AOP的一种简单形式。
试想,如果有一种方法可以将上面所有的方法合起来成为一种方法,使得代码回到最初始状态(只有业务逻辑),那将是最好的了。那就读起来最简单,有最少的构造函数注入的服务。当业务逻辑变化时,我们也不必担心忘记或忽略了这些横切关注点,从而减少了变更的代价。
变更的代价
软件工程中不变的东西就是变化,需求变了,业务规则变了,技术变了。业务逻辑或需求的任何变更对处理原始版本的业务逻辑都是挑战性的(在代码重构之前)。
需求变更
因为许多原因,需求会变更。需求一开始可能是很模糊的,但是随着软件开始成型,就会变得更加具体。项目经理等人就会改变想法,对他们来说看似很小的变化,可能在代码中意味着很大的不同。
虽然我们都知道需求会变是个真理,并且也已经反复见证了,但仍然在犯一个错,那就是编码时好像什么都不会改变。作为一个好的开发者,不仅要接受需求的变化,还要期待需求变化。
项目的大小确实很重要,如果你是一个人编写一个简单的软件(比如一个具有两三个表单和许多静态内容的网站),那么变更的代价可能很低,因为改动的地方很少。
方法签名变更
给方法添加或移除参数就会导致方法签名变更。如果移除了一个参数,就必须移除该参数的防御性编程,否则,项目编译不通过。如果修改了一个参数的类型,那么防御性编程边界情况也会改变。更危险的是,如果添加了一个参数,就必须添加该参数的防御性编程,不幸的似乎,编译器不会帮你做这个,自己必须要记得做这件事。
看一下之前的Accrue 方法,签名改变的地方会立即影响防御编程和日志记录,如下:
public void Accrue(RentalAgreement agreement) {
// defensive programming
if(agreement == null) throw new ArgumentNullException("agreement");
// logging
Console.WriteLine("Accrue: {0}", DateTime.Now);
Console.WriteLine("Customer: {0}", agreement.Customer.Id);
Console.WriteLine("Vehicle: {0}", agreement.Vehicle.Id);
// ... snip ...
// logging
Console.WriteLine("Accrue complete: {0}", DateTime.Now);
}
如果参数名从agreement 变成rentalAgreement ,那么必须记得更改ArgumentNullException 的构造函数的字符串参数。如果方法名本身变了,也必须更改logging中记录的字符串方法名。虽然有很多重构工具可以辅助,如Resharp,但是其他的还要依赖你自己和团队的警惕。
团队开发
一个人开发就算了。假设有个新的需求,ILoyaltyAccureService 接口需要添加一个新的方法,也许这个任务会派给其他队友,并且这个队友实现了业务逻辑并完成了任务。不幸地是,这个队友忘记了使用TransactionFacade 的Wrapper 方法,他的代码通过了UT,然后交给了QA。如果这是一个敏捷项目,这也许不是大问题:QA会捕捉到这个问题,并立即把这个问题报告给你。在一个瀑布项目中,QA可能在几个月之后才会发现这个bug。几个月后,你可能也不记得造成这个bug的原因了。就好像你是团队中的新员工一样。
最糟糕的情况:它可能通过了QA,假设的异常或重试条件不是必要的或者没有被注意到,这样,代码就没有经过防御性编程、logging、事务等等进入了生产环境,这样迟早出问题!
使用AOP重构
再次重构代码,这次使用AOP,使用NuGet添加Postsharp到项目CarRental.Core 中,关于如何添加,请查看上一篇文章。
开发简单、独立的logging (编辑:徐州站长网)
【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!
|