在展开问题之前我们首先设定一个例子,在这个示例中我将使用尽可能简单的逻辑实现所有功能需求,这将更突出我们所要解决的核心问题。例子是一个简单计算器类:
public class Calculator
{
public int Add(int x, int y) { return x + y; }
}
测试代码如下(你可以使用NUnit与我们一起完成对这个例子的研究):
public void Test()
{
Calculator calculator = new Calculator();
Assert.IsNotNull(calculator);
Assert.AreEqual(5, calculator.Add(2,3));
}
这个类再简单不过了,不过若你将它想象为一个可能更复杂的业务处理类的时候,你将面临除了核心功能实现之外的更多处理细节,比如说:权限控制、审计日志、性能监测、缓冲处理、事务环境等等。为简单起见,我们首先为该类增加记录日志的功能,该功能要求将对每个方法的调用和处理结果输出到Console中,如下:
public class Calculator
{
public int Add(int x, int y)
{
Console.Write("Add({0},{1})", x, y);
int result = x + y;
Console.WriteLine(" = {0}", result);
return result;
}
}
再简单不过了,对吧?现在我们需要为该方法实现性能监测,如下:
public class Calculator
{
public int Add(int x, int y)
{
Console.Write("Add({0},{1})", x, y);
DateTime startTime = PreciseTimer.Now;
int result = x + y;
TimeSpan elapsedTime = PreciseTimer.ElapsedSince(start);
Console.Write(" [{0}] ", elapsedTime);
Console.WriteLine(" = {0}", result);
return result;
}
}
这里暂时不要去管PreciseTimer这个类,它只是一个用来计时的工具而已,跟我们的核心问题没什么大关系。此时你已经感觉到,虽然我们实现了所需的功能,但是在一个方法中堆叠了处理各类事宜(Aspect! I heard you! :)的不同代码。虽然在这个简单例子中不会感觉有什么不爽,但是请你想象一下如果我们将为该类添加第二个方法时会发生什么事情:
public class Calculator
{
public int Add(int x, int y)
{
Console.Write("Add({0},{1})", x, y);
DateTime startTime = PreciseTimer.Now;
int result = x + y;
TimeSpan elapsedTime = PreciseTimer.ElapsedSince(start);
Console.Write(" [{0}] ", elapsedTime);
Console.WriteLine(" = {0}", result);
return result;
}
public int Subtract(int x, int y)
{
Console.Write("Subtract({0},{1})", x, y);
DateTime startTime = PreciseTimer.Now;
int result = x - y;
TimeSpan elapsedTime = PreciseTimer.ElapsedSince(start);
Console.Write(" [{0}] ", elapsedTime);
Console.WriteLine(" = {0}", result);
return result;
}
}
此时的单元测试代码补充如下:
public void Test()
{
ICalculator calculator = new Calculator();
Assert.IsNotNull(calculator);
Assert.AreEqual(5, calculator.Add(2,3));
Assert.AreEqual(5, calculator.Subtract(8,3));
}
在两个方法中已经明显出现重复代码了,这可不是一个好的smell——想想一下如果我们的计算器有10个方法呢?如果我们还有类似于计算器类的另外数十个类呢?如果我们还有更多的方法级功能要实现呢(权限控制、事务管理……)?在企业级应用开发中,这可是一个经常会遇的问题。为清楚起见,我们将问题分解成两部分,首要的问题是代码职责混淆,其次则是同样的代码逻辑反复多次——这些问题都将导致开发管理、代码编写与维护的各种困难。
为了解决代码职责混淆的问题,我们首先考虑的是使用DECORATOR设计模式将不同职责的代码分解到若干个DECORATOR中,并继而使用一种对象构造模式(如ABSTRACT FACTORY或FACTORY METHOD或BUILDER等等,都可以)在运行时将DECORATOR与核心实现组装起来。为了实现DECORATOR模式,我们首先需要使用Extract Interface的重构技术提取接口,如下:
public interface ICalculator
{
int Add(int x, int y);
int Subtract(int x, int y);
}
public class Calculator: ICalculator
{
public int Add(int x, int y)
{
Console.Write("Add({0},{1})", x, y);
DateTime startTime = PreciseTimer.Now;
int result = x + y;
TimeSpan elapsedTime = PreciseTimer.ElapsedSince(start);
Console.Write(" [{0}] ", elapsedTime);
Console.WriteLine(" = {0}", result);
return result;
}
public int Subtract(int x, int y)
{
Console.Write("Subtract({0},{1})", x, y);
DateTime startTime = PreciseTimer.Now;
int result = x - y;
TimeSpan elapsedTime = PreciseTimer.ElapsedSince(start);
Console.Write(" [{0}] ", elapsedTime);
Console.WriteLine(" = {0}", result);
return result;
}
}
测试代码相应修改如下:
public void Test()
{
ICalculator calculator = new Calculator();
Assert.IsNotNull(calculator);
Assert.AreEqual(5, calculator.Add(2,3));
Assert.AreEqual(5, calculator.Subtract(8,3));
}
运行测试代码可以确信现在Calculator的行为未受到任何影响(这是正确使用重构技术的重要标志噢),那么我们开始剥离负责性能监测职责的代码并将其作为一个DECORATOR实现:
public class Calculator: ICalculator
{
public int Add(int x, int y)
{
Console.Write("Add({0},{1})", x, y);
DateTime startTime = PreciseTimer.Now;
int result = x + y;
TimeSpan elapsedTime = PreciseTimer.ElapsedSince(start);
Console.Write(" [{0}] ", elapsedTime);
Console.WriteLine(" = {0}", result);
return result;
}
public int Subtract(int x, int y)
{
Console.Write("Subtract({0},{1})", x, y);
DateTime startTime = PreciseTimer.Now;
int result = x - y;
TimeSpan elapsedTime = PreciseTimer.ElapsedSince(start);
Console.Write(" [{0}] ", elapsedTime);
Console.WriteLine(" = {0}", result);
return result;
}
}
public class CalculatorTimer: ICalculator
{
public int Add(int x, int y)
{
DateTime startTime = PreciseTimer.Now;
int result = Decoratee.Add(x, y);
TimeSpan elapsedTime = PreciseTimer.ElapsedSince(start);
return result;
}
public int Subtract(int x, int y)
{
DateTime startTime = PreciseTimer.Now;
int result = Decoratee.Subtract(x, y);
TimeSpan elapsedTime = PreciseTimer.ElapsedSince(start);
return result;
}
}
可以看到,在负责性能监测(在这个简单例子中我们还不如就叫它timer吧:)的DECORATOR中,代码只关注计时的功能,而将核心的工作交由decoratee(自造词,别介意——caller/callee、tester/testee、assigner/assignee、bomber/bombee……:)去处理。这里的decoratee也是一个ICalculator类型的接口引用,我们需要在构造decorator的时候为其设置好decoratee,这个工作将在对象工厂中实现。这里我一次到位的为所有可能的DECORATOR实现一个基类,该类实现一个新的接口ICalculatorDecorator,在这个接口中我们声明一个支持读写decoratee的语义,如下:
public interface ICalculatorDecorator
{
void InitDecorator(ICalculator decoratee);
ICalculator Decoratee { get; }
}
然后我们为所有的Decorator提供该接口的一个基本实现:
public abstract class CalculatorDecorator: ICalculatorDecorator
{
private ICalculator decoratee;
void ICalculatorDecorator.InitDecorator(ICalculator decoratee)
{
this.decoratee = decoratee;
}
// FIXED: to use implicit interface implementation instead explicit way
// so that derived classes could access this property. Thanks greatqn!
ICalculator ICalculatorDecorator.Decoratee
public ICalculator Decoratee
{
get { return this.decoratee; }
}
}
再由此派生出之前的CalculatorTimer:
public class CalculatorTimer: CalculatorDecorator, ICalculator
{
public int Add(int x, int y)
{
DateTime startTime = PreciseTimer.Now;
int result = Decoratee.Add(x, y);
TimeSpan elapsedTime = PreciseTimer.ElapsedSince(start);
return result;
}
public int Subtract(int x, int y)
{
DateTime startTime = PreciseTimer.Now;
int result = Decoratee.Subtract(x, y);
TimeSpan elapsedTime = PreciseTimer.ElapsedSince(start);
return result;
}
}
为了将这个负责性能监测职能的DECORATOR与其被装饰者(decoratee)组合起来,我们需要一个对象工厂类(它同时可以将调用代码逻辑与对象创建逻辑解耦):
public class CalculatorFactory
{
private CalculatorFactory() {}
private static Type[] GetObjectGraph()
{
ArrayList objectGraphTypes = new ArrayList();
// Add decoratee as 1st type to be created
objectGraphTypes.Add(typeof(Calculator));
// and then add all decorators
objectGraphTypes.Add(typeof(CalculatorTimer));
return (Type[])objectGraphTypes.ToArray(typeof(Type));
}
public static ICalculator CreateInstance()
{
Type[] objectGraphTypes = GetObjectGraph();
ICalculator result = null;
foreach (Type calcType in objectGraphTypes)
{
ICalculator calcImpl = (ICalculator)Activator.CreateInstance(calcType);
if (calcImpl is ICalculatorDecorator)
{
((ICalculatorDecorator)calcImpl).InitDecorator(result);
}
result = calcImpl;
}
return result;
}
}
相应修改单元测试使其使用这个工厂的CreateInstance()方法(这实际是设计模式中所谓的FACTORY METHOD),如下:
public void Test()
{
ICalculator calculator = CalculatorFactory.CreateInstance();
Assert.IsNotNull(calculator);
Assert.AreEqual(5, calculator.Add(2,3));
Assert.AreEqual(5, calculator.Subtract(8,3));
}
在这个类里面我们提供了一个静态方法CreateInstance()用来返回ICalculator的一个实现。在这个实现逻辑中,我们首先构造一个对象组装图(object graph),其实就是一个类型列表,按照从内到外的顺序依次添加若干ICalculator或ICalculatorDecorator的实现类型。接下来,一个foreach循环按照构造列表依次创建每一个类型,并在创建后判断新创建的类型是否是一个ICalculatorDecorator,如果是的话,我们将已然创建好的最后一个ICalculator实现(即result)作为decoratee赋予它以实现对象组合。循环完成时,result所引用的ICalculator实现就是最外面的一个decorator,而整个调用链其实是通过每一个DECORATOR类的方法中的Decoratee.XXX()来构成的。
有了这个改进的对象结构,我们可以很容易的添加新的DECORATOR为对象增加新的职责,如将之前的日志记录逻辑实现如下:
public class CalculatorLogger: CalculatorDecorator, ICalculator
{
public int Add(int x, int y)
{
Console.Write("Add({0}, {1})", x, y);
int result = Decoratee.Add(x, y);
Console.WriteLine(" = {0}", result);
return result;
}
public int Subtract(int x, int y)
{
Console.Write("Subtract({0}, {1})", x, y);
int result = Decoratee.Subtract(x, y);
Console.WriteLine(" = {0}", result);
return result;
}
}
接下来只要在对象工厂的CreateInstance()方法中将其加入对象构造图中即可:
public class CalculatorFactory
{
…
private static Type[] GetObjectGraph()
{
ArrayList objectGraphTypes = new ArrayList();
// Add decoratee as 1st type to be created
objectGraphTypes.Add(typeof(Calculator));
// and then add all decorators
objectGraphTypes.Add(typeof(CalculatorTimer));
objectGraphTypes.Add(typeof(CalculatorLogger));
return (Type[])objectGraphTypes.ToArray(typeof(Type));
}
public static ICalculator CreateInstance() …
}
不过,以我有限的设计经验来看,让所有的DECORATOR显式去调用Decoratee的方法以形成对象关系链是繁琐和易出错的(不过也是效率最好的)。因为如果某一个方法实现没有去调用Decoratee的相应方法的话,整个对象链将被切断。同时我们还发现,随着组件接口的日益复杂,接口中方法的数目越来越多,因此对于每一个新增的DECORATOR而言都需要实现所有这些接口方法,这对于一些并非施加于所有接口方法的DECORATOR而言就显得有些累赘了(如仅对少数方法有意义的缓冲策略DECORATOR等等)。这些现象都提示我们可以使用TEMPLATE METHOD设计模式来为所有的DECORATOR提供一个统一的、灵活的基础实现逻辑,如下:
public abstract class CalculatorDecorator: ICalculatorDecorator, ICalculator
{
private ICalculator decoratee;
void ICalculatorDecorator.InitDecorator(ICalculator decoratee)
{
this.decoratee = decoratee;
}
public ICalculator Decoratee
{
get { return this.decoratee; }
}
int ICalculator.Add(int x, int y)
{
DoPreAdd(ref x, ref y);
int result = decoratee.Add(x, y);
DoPostAdd(x, y, ref result);
return result;
}
int ICalculator.Subtract(int x, int y)
{
DoPreSubtract(ref x, ref y);
int result = decoratee.Subtract(x, y);
DoPostSubtract(x, y, ref result);
return result;
}
protected virtual void DoPreAdd(ref int x, ref int y) {}
protected virtual void DoPostAdd(int x, int y, ref result) {}
protected virtual void DoPreSubtract(ref int x, ref int y) {}
protected virtual void DePostSubtract(int x, int y, ref result) {}
}
在这个基于TEMPLATE METHODS设计模式的DECORATOR基类中,我们显式的实现了ICalculator接口所有方法的模板逻辑,模板中可变的部分委托给若干protected virtual的模板方法实现,这些模板方法继而可被派生类可选择的override(因为是virtual的)。当然,如果你希望所有的DECORATOR一定要提供某一个环节的模板方法实现,你也可以使用protected abstract来修饰这个模板方法,这样一来派生类就必须要实现这个方法才能够通过编译了。另外,虽然我们在基类上使用了显式接口实现,但是派生类仍然可以重新提供相关接口的实现,这种灵活度足以满足各种应用场合了(参见C#语言规范之13.4.1和13.4.4)。
还有一个问题需要解决,即DECORATOR之间的相互协作在目前的设计中是不可以完成的。为什么要DECORATOR之间要协作?举个简单例子,如果我们需要在Logger中顺便记录方法执行的时间,而这个时间是由Timer来测定的话,我们就需要在Logger的实现中取得由Timer记录下来的时长信息(当然,这同时要求Logger位于Timer的外层,这样当Timer返回后Logger才可能获取到Timer记录的信息)。为了达到这个目的,需要为所有组成对象组合的对象实例引入一个公共的信息容器(比如一个使用Hashtable实现的数据字典),不妨称其为Context。我们希望所有的DECORATOR都可以取得这个共享的容器,因此首先我们扩充ICalculatorDecorator接口及其实现:
public interface ICalculatorDecorator
{
void InitDecorator(ICalculator decoratee, IDictionary context);
ICalculator Decoratee { get; }
IDictionary Context { get; }
}
public abstract class CalcuatorDecorator: ICalculatorDecorator
{
private ICalculator decoratee;
private IDictionary context;
public void InitDecorator(ICalculator decoratee, IDictionary context)
{
this.decoratee = decoratee;
this.context = context;
}
public ICalculator Decoratee
{
get { return this.decoratee; }
}
public IDictionary Context
{
get { return this.context; }
}
}
接下来,我们在对象工厂创建对象的时候为复合对象创建并设置context:
public class CalculatorFactory
{
…
public static ICalculator CreateInstance()
{
Type[] objectGraphTypes = GetObjectGraph();
ICalculator result = null;
IDictionary context = new Hashtable();
foreach (Type calcType in objectGraphTypes)
{
ICalculator calcImpl = (ICalculator)Activator.CreateInstance(calcType);
if (calcImpl is ICalculatorDecorator)
{
((ICalculatorDecorator)calcImpl).InitDecorator(result, context);
}
result = calcImpl;
}
return result;
}
}
这样,在DECORATOR(也即CalculatorDecorator的派生类)的内部就可以使用Context属性访问整个对象组合中各个DECORATOR的共享存储环境:
public class CalculatorTimer: CalculatorDecorator, ICalculator
{
public int Add(int x, int y)
{
DateTime startTime = PreciseTimer.Now;
int result = Decoratee.Add(x, y);
TimeSpan elapsedTime = PreciseTimer.ElapsedSince(start);
Context["Add.ElapsedTime"] = elapsedTime;
return result;
}
public int Subtract(int x, int y)
{
DateTime startTime = PreciseTimer.Now;
int result = Decoratee.Subtract(x, y);
TimeSpan elapsedTime = PreciseTimer.ElapsedSince(start);
Context["Subtract.ElapsedTime"] = elapsedTime;
return result;
}
}
public class CalculatorLogger: CalculatorDecorator, ICalculator
{
public int Add(int x, int y)
{
Console.Write("Add({0}, {1})", x, y);
int result = Decoratee.Add(x, y);
Console.WriteLine(" = {0} {1}", result, Context["Add.ElapsedTime"]);
return result;
}
public int Subtract(int x, int y)
{
Console.Write("Subtract({0}, {1})", x, y);
int result = Decoratee.Subtract(x, y);
Console.WriteLine(" = {0} {1}", result, Context["Subtract.ElapsedTime"]);
return result;
}
}
本例进展至此已经形成了一定的对象结构,让我们小结一下吧。首先,我们提出了一个问题,即在企业系统开发中因为既要满足功能性需求,也需要照顾很多不同方面的非功能性需求,这使得对象方法的实现变得越来越复杂、繁琐、僵硬和难以维护(虽然从执行效率的角度看还是最优化的);接下来我们引入对象结构设计模式DECORATOR将对象必须实现的各方面代码分散到以接口为基础的核心实现对象和若干修饰对象,并进而引入对象创建设计模式将这些对象的构造过程封装起来,通过接口保持与调用代码的松散耦合;最后,我们对已经形成的对象结构进行小的修正与扩充,使其允许各个DECORATOR通过一个共享的Context相互通信。
经过这个设计过程,我们最终形成了一个比起最初模型而言更易于扩展的、松散耦合的、较易维护的对象模型,然而我们也面临一个问题:如果我们有很多这样的业务组件都需要同样的一些DECORATOR的话,我们仍然会面临很多重复代码,而且这些代码在面向对象的范畴内是无法消除的。什么意思?以Logger为例,如果给几十个组件的数百个方法都去实现logging的DECORATOR,无疑仍然是个巨大的开发和维护工作。为什么不可以通过面向对象的技术消除?因为我们希望消除的重复是在方法的层次上,而方法已经是对象内部封装的范畴,很难为不同的类型的每个方法提供一个统一的实现(如果要有也应该在所有对象的一个公共基类中——Object?显然进了死胡同……)。那么这个问题该如何解决呢(尤其是在.NET的环境中)?我将在本文的下篇中结合CLR中提供的相关机制探讨在.NET环境下进行AOP(Aspect-Oriented Programming,面向方面的编程思想)编码的相关技术,包括CLR中的TranspantProxy/RealProxy、MarshalByRefObject/ContextBoundObject、Context Attribute/Context Property/IMessageSink等等。