目录
介绍
装饰器模式是一种引人入胜且非常流行的图案。该模式旨在动态地向现有对象添加附加功能。装饰器为扩展功能提供了子类化的替代方法。虽然可以通过对象类的子类化来为整个对象类添加功能,但装饰器模式旨在仅向单个对象添加功能,而使该类的其他对象保持不变。
有时有人评论说,通过在不修改原始类型的情况下增强现有类型,该模式遵循开闭原则。
非常有趣的是,该模式通过违背OO建议的错误方式来实现其目标。
组合与继承:如何选择?
OOA/OOD课程中的一个典型问题是,对于给定的类/对象A,在创建要重用类/对象A的新类/对象B时,如何以及基于什么标准在组合和继承之间进行选择?通常会给出建议:如果您打算重用现有类A的Public接口,请使用继承。如果您打算仅重用现有类A的功能,请选择组合。
所以,装饰器模式正好相反。它计划重用现有类的Public接口,但使用组合。这个决定对Public接口扩展的问题有一些影响,我们稍后会看到。
经典装饰器模式——第1版
经典装饰器是GoF书中提供的模式的一个版本,在文献中经常提到。通常,您会有一些接口IComponent抽象一些真实的类ComponentA。我们的愿望是用一些可以提供更多/增强功能的对象替换类ComponentA的对象,我们称之为DecoratorB,其实现相同接口IComponent的对象。我们希望它替换的内容:
IComponent comp= new ComponentA();
和:
IComponent comp= new DecoratorB(/*--some parameters--*/);
其中,DecoratorB以某种方式 “改进”object ComponentA。
在这种模式中,DecoratorB用于组合,将其添加为ComponentA组件,并从头开始重新实现IComponent接口,有时只是将ComponentA方法调用传递给方法。
所以,让我们假设我们有这个IComponent接口和ComponentA类实现:
public interface IComponent //(1)
{
string StateI { get; set; } //(2)
string MethodI1(bool print = true); //(3)
}
public class ComponentA : IComponent //(4)
{
public string StateI { get; set; } //(5)
public string StateA { get; set; } //(6)
public ComponentA() //(7)
{ }
public string MethodI1(bool print = true) //(8)
{
string text = "ComponentA.MethodI1";
if (print) Console.WriteLine(text);
return text;
}
public string MethodA1() //(9)
{
string text = "ComponentA.MethodA1";
Console.WriteLine(text);
return text;
}
}
如果您查看该IComponent接口,您会看到它公开了一个属性(2)和一个方法(3)。
类ComponentA是我们的核心组件/功能类。可以看到,它当然实现了IComponent (5), (8)的public接口,但除此之外,在(6)、(9)处还对类public接口做了一些补充。当通接口IComponent访问类时,我们当然受限于该接口公开的属性/方法,这意味着属性(6)和方法(9)将无法访问。
上述类的典型用法如下所示:
Console.WriteLine("IComponent================================");
IComponent I = new ComponentA(); //(20)
I.StateI = "123";
//I.StateA = "123"; //not possible
string tmpI1 = I.MethodI1(); //(21)
//string tmpI2 = I.MethodA1(); //not possible //(22)
Console.WriteLine("ComponentA================================");
ComponentA A = new ComponentA(); //(23)
A.StateI = "123";
A.StateA = "123";
string tmpA1 = A.MethodI1();
string tmpA2 = A.MethodA1();
现在让我们看看示例装饰器类的样子:
public class DecoratorB : IComponent //(10)
{
public string StateI { get; set; } //(11)
public string StateB { get; set; } //(12)
private IComponent component = null; //(13)
public DecoratorB(IComponent comp) //(14)
{
component = comp;
}
public string MethodI1(bool print = true) //(15)
{
string baseTmp = component.MethodI1(false);
string text = baseTmp + " " + "DecoratorB.MethodI1";
if (print) Console.WriteLine(text);
return (text);
}
public string MethodB1() //(16)
{
string text = "DecoratorB.MethodB1";
Console.WriteLine(text);
return text;
}
}
Decorator类中要注意的关键是传递IComponent的构造函数(14)如何分配给private属性(13)。这就是我们所说的“组合”,装饰器的所有魔力都由此而来。
Decorator(10)现在可以完全重用(13)中包含的对象的功能并公开其自己的接口,在该接口中可以自由地重用或修改对象(13)的行为。
在(15)方法MethodI1中,我们看到DecoratorB如何提供自己的接口(10)实现,并且在该过程中,它重用MethodI1包含的对象(13)。
除了它在(10)中继承的public接口之外,DecoratorB还可以添加自己的属性(12)和方法(16),从而为类添加一些新功能。
DecoratorB的典型用法如下所示:
DecoratorB B = new DecoratorB(new ComponentA()); //(24)
B.StateI = "123";
//B.StateA = "123"; //not possible
B.StateB = "123";
string tmpB1 = B.MethodI1(); //(25)
//string tmpB2 = B.MethodA1(); //not possible //(26)
string tmpB3 = B.MethodB1();
看调用(25)。这就是装饰器模式的使用和强度的真正原因。那就是对类DecoratorB的MethodI1调用,就是使用和增强对类ComponentA的MethodI1调用。由于MethodI1是接口IComponent的一部分,即使结果对象(24)是通过对IComponent的引用访问的,该调用也将可用并且它将起作用。引用IComponent的消费者甚至不需要知道他使用的是原始ComponentA或装饰版本。我们为什么要使用这个装饰器模式的全部意义在于这个调用的力量。当然,这是教程级别的代码,在现实生活中会有更多的方法MethodI1,MethodI2,MethodI3,等等,都具有这种能力。
我们可以看到,在(26)中,无法从ComponentA访问未通过接口IComponent公开的方法。这是一个问题,因为我们可能有多个组件,比如ComponentA、ComponentA2、ComponentA3等,并且每个组件都可能有自己特定的设置器来配置自己。发生这种情况的主要原因是,同样的,我们不继承自ComponentA,我们包含类ComponentA的对象,我们只能通过接口IComponent访问它。
这是装饰器模式的一个严重缺陷,因为ComponentA在准备好被DecoratorB重用之前可能需要一些配置/设置。它通常以两种方式绕过:
- ComponentA在传递到DecoratorB(24)之前配置
- ComponentA构造函数将有参数来配置自己
让我们再创建一个Decorator类:
public class DecoratorC : IComponent //(40)
{
public string StateI { get; set; }
public string StateC { get; set; } //(41)
private IComponent component = null;
public DecoratorC(IComponent comp)
{
component = comp;
}
public string MethodI1(bool print = true)
{
string baseTmp = component.MethodI1(false);
string text = baseTmp + " " + "DecoratorC.MethodI1";
if (print) Console.WriteLine(text);
return text;
}
public string MethodC1() //(42)
{
string text = "DecoratorC.MethodC1";
Console.WriteLine(text);
return text;
}
}
如您所见,DecoratorC几乎与DecoratorB一样。 这是为了本文的目的而故意这样做的。
请注意,在(41)和(42)DecoratorC中定义了一些特定于自身的public属性/方法。
这是现在DecoratorC的用法:
DecoratorC C = new DecoratorC(new DecoratorB(new ComponentA())); //(27)
C.StateI = "123";
//C.StateA = "123"; //not possible
//C.StateB = "123"; //not possible
C.StateC = "123";
string tmpC1 = C.MethodI1(); //(28)
//string tmpC2 = C.MethodA1(); //not possible //(29)
//string tmpC3 = C.MethodB1(); //not possible //(30)
string tmpC4 = C.MethodC1();
您可以在(27)中看到如何将多个装饰器应用于一个组件。让我们立即说,从代码设计的角度来看,没有什么能阻止我们以不同的顺序应用装饰器,而不是 第一个是DecoratorB之后是DecoratorC,我们可以以不同的顺序应用它们。但是,生成的功能可能会有所不同,因此顺序是相关的。
看调用(28)。同样,这也是装饰器模式的使用和强度的真正原因。那是对类DecoratorC的MethodI1调用,即使用和增强对类DecoratorB的MethodI1调用,也就是使用和增强对类ComponentA的MethodI1调用。同样,如果我们将(27)分配给对IComponent的引用,则引用IComponent的消费者甚至不需要知道他使用的是原始版本的ComponentA还是修饰版本。我们为什么要使用这种Decorator模式的全部意义在于这个调用的力量。再一次,在现实生活中,会有更多的方法MethodI1, MethodI2, MethodI3等都具有这种能力。
您可以在(29)和(30)中看到,访问ComponentA和DecoratorB的某些public方法是不可能的。如上所述,这是装饰器模式的严重缺陷。
下面是整个项目经典装饰器的类图和代码。
public interface IComponent //(1)
{
string StateI { get; set; } //(2)
string MethodI1(bool print = true); //(3)
}
public class ComponentA : IComponent //(4)
{
public string StateI { get; set; } //(5)
public string StateA { get; set; } //(6)
public ComponentA() //(7)
{ }
public string MethodI1(bool print = true) //(8)
{
string text = "ComponentA.MethodI1";
if (print) Console.WriteLine(text);
return text;
}
public string MethodA1() //(9)
{
string text = "ComponentA.MethodA1";
Console.WriteLine(text);
return text;
}
}
public class DecoratorB : IComponent //(10)
{
public string StateI { get; set; } //(11)
public string StateB { get; set; } //(12)
private IComponent component = null; //(13)
public DecoratorB(IComponent comp) //(14)
{
component = comp;
}
public string MethodI1(bool print = true) //(15)
{
string baseTmp = component.MethodI1(false);
string text = baseTmp + " " + "DecoratorB.MethodI1";
if (print) Console.WriteLine(text);
return (text);
}
public string MethodB1() //(16)
{
string text = "DecoratorB.MethodB1";
Console.WriteLine(text);
return text;
}
}
public class DecoratorC : IComponent //(40)
{
public string StateI { get; set; }
public string StateC { get; set; } //(41)
private IComponent component = null;
public DecoratorC(IComponent comp)
{
component = comp;
}
public string MethodI1(bool print = true)
{
string baseTmp = component.MethodI1(false);
string text = baseTmp + " " + "DecoratorC.MethodI1";
if (print) Console.WriteLine(text);
return text;
}
public string MethodC1() //(42)
{
string text = "DecoratorC.MethodC1";
Console.WriteLine(text);
return text;
}
}
class Client
{
static void Main(string[] args)
{
Console.WriteLine("IComponent================================");
IComponent I = new ComponentA(); //(20)
I.StateI = "123";
//I.StateA = "123"; //not possible
string tmpI1 = I.MethodI1(); //(21)
//string tmpI2 = I.MethodA1(); //not possible //(22)
Console.WriteLine("ComponentA================================");
ComponentA A = new ComponentA(); //(23)
A.StateI = "123";
A.StateA = "123";
string tmpA1 = A.MethodI1();
string tmpA2 = A.MethodA1();
Console.WriteLine("DecoratorB================================");
DecoratorB B = new DecoratorB(new ComponentA()); //(24)
B.StateI = "123";
//B.StateA = "123"; //not possible
B.StateB = "123";
string tmpB1 = B.MethodI1(); //(25)
//string tmpB2 = B.MethodA1(); //not possible //(26)
string tmpB3 = B.MethodB1();
Console.WriteLine("DecoratorC================================");
DecoratorC C = new DecoratorC(new DecoratorB(new ComponentA())); //(27)
C.StateI = "123";
//C.StateA = "123"; //not possible
//C.StateB = "123"; //not possible
C.StateC = "123";
string tmpC1 = C.MethodI1(); //(28)
//string tmpC2 = C.MethodA1(); //not possible //(29)
//string tmpC3 = C.MethodB1(); //not possible //(30)
string tmpC4 = C.MethodC1();
Console.WriteLine("Collection================================");
List<IComponent> list = new List<IComponent>();
list.Add(new ComponentA());
list.Add(new DecoratorB(new ComponentA()));
list.Add(new DecoratorC(new DecoratorB(new ComponentA())));
foreach (IComponent iComp in list)
{
iComp.StateI = "123";
string tmpII1 = iComp.MethodI1();
}
Console.ReadLine();
}
}
以下是示例执行的结果:
动态选择组件类型
装饰器模式使成为可能的是在运行时动态选择将使用哪种类型的组件应用程序。让我们看一下这段代码:
ComponentA compA = new ComponentA();
compA.StateA = "123"; // some configuring
IComponent comp = null;
int selection = GetSelectionFromGui();
switch (selection)
{
case 1:
comp = new DecoratorB(compA);
break;
case 2:
comp = new DecoratorC(compA);
break;
default:
comp = compA;
break;
}
string result = comp.MethodI1();
您可以看到,用户输入将在运行时决定组件类型。
装饰周期
代码中没有任何东西阻止我们多次应用同一个装饰器。例如,看代码:
IComponent cc = new DecoratorB(new DecoratorC(new DecoratorB(new ComponentA())));
在文献[1]中,您可以找到讨论如何检测装饰器循环并可能禁止它们的文章。我们不会在这里讨论这个。
胖经典装饰器的问题
我们说过装饰器模式是关于可组合性的,即重用的类不是继承而是组合成装饰器类。这样做的一个副作用是装饰器不会继承重用类的public接口,而是需要显式地实现每个需要公开重用/公开的方法。如果数量更大,创建和维护所有这些方法需要付出一些努力。
让我们看看类图在胖经典观察者的情况下是什么样子的,它有五个 public方法可以公开。
可以看出,解决方案中的方法数量显着增长。大多数方法可能只是传递对所包含对象的调用,正如在DecoratorB的这个示例实现中所看到的那样。
public class DecoratorB : IComponent
{
public string StateI { get; set; }
public string StateB { get; set; }
private IComponent component = null;
public DecoratorB(IComponent comp)
{
component = comp;
}
public string MethodI1(bool print = true)
{
return component.MethodI1();
}
public string MethodI2(bool print = true)
{
return component.MethodI2();
}
public string MethodI3(bool print = true)
{
return component.MethodI3();
}
public string MethodI4(bool print = true)
{
return component.MethodI4();
}
public string MethodI5(bool print = true)
{
return component.MethodI5();
}
public string MethodB1()
{
string text = "DecoratorB.MethodB1";
Console.WriteLine(text);
return text;
}
}
更现实的是,在实践中,每个类将有20多个方法需要重用/公开。有一些像ReSharper这样的开发人员工具可以帮助开发人员自动创建和维护所有这些方法,但责任仍然在于开发人员/实施者。
泛型装饰器模式——第2版
泛型装饰器模式版本可以在最近的文献中找到。装饰器基于可组合性的思想保持不变,只是传递类型信息的方法不同。从模式的目的和主要设计思想来看,很多东西都是相似的。我从[2]中得到了这个版本的装饰器模式和核心代码的想法。
主要技巧是通过泛型参数类型传递类/类型信息。包含的组件对象的创建被传递给类型参数类的默认构造函数。因此,与我们之前的代码类似的代码示例现在看起来像这样:
IComponent C = new DecoratorC<DecoratorB<ComponentA>>();
由于使用方面的许多事情与以前的版本相似,我们将立即进入类图和代码:
public interface IComponent
{
string StateI { get; set; }
string MethodI1(bool print = true);
}
public class ComponentA : IComponent
{
public string StateI { get; set; }
public string StateA { get; set; }
public ComponentA()
{ }
public string MethodI1(bool print = true)
{
string text = "ComponentA.MethodI1";
if (print) Console.WriteLine(text);
return text;
}
public string MethodA1()
{
string text = "ComponentA.MethodA1";
Console.WriteLine(text);
return text;
}
}
public class DecoratorB<T> : IComponent //(50)
where T : IComponent, new()
{
public string StateI { get; set; }
public string StateB { get; set; }
private T component = new T(); //(51)
public DecoratorB()
{
}
public string MethodI1(bool print = true)
{
string baseTmp = component.MethodI1(false);
string text = baseTmp + " " + "DecoratorB.MethodI1";
if (print) Console.WriteLine(text);
return (text);
}
public string MethodB1()
{
string text = "DecoratorB.MethodB1";
Console.WriteLine(text);
return text;
}
}
public class DecoratorC<T> : IComponent
where T : IComponent, new()
{
public string StateI { get; set; }
public string StateC { get; set; }
private T component = new T();
public DecoratorC()
{
}
public string MethodI1(bool print = true)
{
string baseTmp = component.MethodI1(false);
string text = baseTmp + " " + "DecoratorC.MethodI1";
if (print) Console.WriteLine(text);
return text;
}
public string MethodC1()
{
string text = "DecoratorC.MethodC1";
Console.WriteLine(text);
return text;
}
}
public class Client
{
static void Main(string[] args)
{
Console.WriteLine("ComponentA================================");
ComponentA A = new ComponentA();
A.StateI = "123";
A.StateA = "123";
string tmpA1 = A.MethodI1();
string tmpA2 = A.MethodA1();
Console.WriteLine("DecoratorB================================");
DecoratorB<ComponentA> B = new DecoratorB<ComponentA>();
B.StateI = "123";
//B.StateA = "123"; //not possible
B.StateB = "123";
string tmpB1 = B.MethodI1();
//string tmpB2 = B.MethodA1(); //not possible
string tmpB3 = B.MethodB1();
Console.WriteLine("DecoratorC================================");
DecoratorC<DecoratorB<ComponentA>> C = new DecoratorC<DecoratorB<ComponentA>>();
C.StateI = "123";
//B.StateA = "123"; //not possible
//C.StateB = "123"; //not possible
C.StateC = "123";
string tmpC1 = C.MethodI1();
//string tmpC2 = C.MethodA1(); //not possible
//string tmpC3 = C.MethodB1(); //not possible
string tmpC4 = C.MethodC1();
Console.WriteLine("Collection================================");
List<IComponent> list = new List<IComponent>();
list.Add(new ComponentA());
list.Add(new DecoratorB<ComponentA>());
list.Add(new DecoratorC<DecoratorB<ComponentA>>());
foreach (IComponent comp in list)
{
comp.StateI = "123";
comp.MethodI1();
}
Console.ReadLine();
}
}
这是执行的结果:
需要注意的主要新事物是Decorator类在(50)中是如何定义的,作为一个泛型类,其类型参数表示重用(装饰)类/对象。还要注意(51)中如何创建需要具有默认构造函数的组件。
其余代码,尤其是装饰器的用法是相同的。
请注意,在类图(和代码)中,我们没有触及或更改IComponent和ComponentA类,而只是DecoratorB和DecoratorC类。
这个版本的装饰器模式并没有带来太多新的东西。它甚至是限制性的,因为它严格依赖于装饰(重用)类的默认构造函数。在学术上,使用泛型展示装饰器版本很有吸引力,但实际上并没有获得新的/更好的功能。
动态选择组件类型的问题
泛型装饰器存在类类型在编译时静态解析的问题。因此,创建类似于之前为经典装饰器显示的代码会产生问题,该代码将在运行时选择组件的类型。可能有一个大的switch声明,列举静态不同的可能组合,但它不像经典装饰器那样自然和容易。
动态装饰器模式——第3版
动态装饰器模式利用动态对象和反射的C#技术来实现比经典装饰器更多的功能。此版本由C#技术专门启用,可能无法在某些不支持类似技术的其他OO语言中实现。
我不知道是否有人独立发布过这样的版本,但是当我阅读有关动态代理的内容时,我想到了它。我想到类似的技术可以用来克服经典装饰器模式的局限性。
关键思想是拦截对未知方法的调用,并将调用重定向到包含的对象进行处理。然后,如果包含的对象无法解析方法调用,则会递归地将方法调用传递给其包含的对象,等等。我们要强调的是,这里的主要技巧是传递方法调用满足层次结构,而不是继承等级制度。
此处显示的代码是演示概念级别的质量,对于生产中的使用可能需要一些改进。
让我们先展示类图和代码,然后我们将讨论:
public interface IComponent
{
string StateI { get; set; }
string MethodI1(bool print = true);
}
public class ComponentA : IComponent
{
public string StateI { get; set; }
public string StateA { get; set; }
public ComponentA()
{ }
public string MethodI1(bool print = true)
{
string text = "ComponentA.MethodI1";
if (print) Console.WriteLine(text);
return text;
}
public string MethodA1()
{
string text = "ComponentA.MethodA1";
Console.WriteLine(text);
return text;
}
}
public class DecoratorB : DynamicObject, IComponent //(60)
{
public string StateI { get; set; }
public string StateB { get; set; }
private dynamic component;
public DecoratorB(IComponent comp)
{
component = comp;
}
public override bool TryInvokeMember( //(61)
InvokeMemberBinder binder, object[] args, out object result)
{
try
{
result = null;
MethodInfo mInfo =
component.GetType().GetMethod(binder.Name); //(62)
if (mInfo != null)
{
result = mInfo.Invoke(component, args); //(63)
}
else
{
if (component is DynamicObject)
{
component.TryInvokeMember( //(64)
binder, args, out result);
}
}
return true;
}
catch
{
result = null;
return false;
}
}
public override bool TrySetMember( //(65)
SetMemberBinder binder, object value)
{
try
{
PropertyInfo prop = component.GetType().GetProperty(
binder.Name, BindingFlags.Public | BindingFlags.Instance);
if (prop != null && prop.CanWrite)
{
prop.SetValue(component, value, null);
}
else
{
if (component is DynamicObject)
{
component.TrySetMember(binder, value);
}
}
return true;
}
catch
{
return false;
}
}
public override bool TryGetMember( //(66)
GetMemberBinder binder, out object result)
{
try
{
result = null;
PropertyInfo prop = component.GetType().GetProperty(
binder.Name, BindingFlags.Public | BindingFlags.Instance);
if (prop != null && prop.CanWrite)
{
result = prop.GetValue(component, null);
}
else
{
if (component is DynamicObject)
{
component.TryGetMember(binder, out result);
}
}
return true;
}
catch
{
result = null;
return false;
}
}
public string MethodI1(bool print = true)
{
string baseTmp = component.MethodI1(false);
string text = baseTmp + " " + "DecoratorB.MethodI1";
if (print) Console.WriteLine(text);
return (text);
}
public string MethodB1()
{
string text = "DecoratorB.MethodB1";
Console.WriteLine(text);
return text;
}
}
public class DecoratorC : DynamicObject, IComponent
{
//similar to code DecoratorB
//removed for brevity
}
class Client
{
static void Main(string[] args)
{
Console.WriteLine("ComponentA================================");
ComponentA A = new ComponentA();
A.StateI = "III";
Console.WriteLine(A.StateI);
A.StateA = "AAA"; // not possible in Classic Decorator
Console.WriteLine(A.StateA);
string tmpA1 = A.MethodI1();
string tmpA2 = A.MethodA1();
Console.WriteLine("DecoratorB================================");
dynamic B = new DecoratorB(new ComponentA());
B.StateI = "III";
Console.WriteLine(B.StateI);
B.StateA = "AAA"; // not possible in Classic Decorator
Console.WriteLine(B.StateA);
B.StateB = "BBB";
Console.WriteLine(B.StateB);
B.StateXXX = "XXX"; // property does not exist, but no exception
string tmpB1 = B.MethodI1();
string tmpB2 = B.MethodA1();
string tmpB3 = B.MethodB1();
B.MethodXXX(); // method does not exist, but no exception
Console.WriteLine("DecoratorC================================");
dynamic C = new DecoratorC(new DecoratorB(new ComponentA())); //(70)
C.StateI = "III";
C.StateA = "AAA"; // not possible in Classic Decorator
Console.WriteLine(C.StateA);
C.StateB = "BBB"; // not possible in Classic Decorator
Console.WriteLine(C.StateB);
C.StateC = "CCC";
Console.WriteLine(C.StateC);
C.StateXXX = "XXX"; // property does not exist, but no exception
string tmpC1 = C.MethodI1();
string tmpC2 = C.MethodA1(); //(71)
string tmpC3 = C.MethodB1(); //(72)
string tmpC4 = C.MethodC1();
C.MethodXXX(); // method does not exist, but no exception //(73)
Console.WriteLine("Collection================================");
List<IComponent> list = new List<IComponent>();
list.Add(new ComponentA());
list.Add(new DecoratorB(new ComponentA()));
list.Add(new DecoratorC(new DecoratorB(new ComponentA())));
foreach (IComponent iComp in list)
{
iComp.StateI = "III";
Console.WriteLine(iComp.StateI);
string tmpI1 = iComp.MethodI1();
}
Console.ReadLine();
}
}
以下是示例执行的结果:
首先,注意在类图和代码类 IComponent和ComponentA中没有改变。只有装饰器的代码发生了变化。这表明该模式可以在需要时替代经典装饰器模式,而无需更改组件代码。
让我们看看DecoratorB类(60)。DecoratorB现在继承自DynamicObject,这给我们带来了创建像(61)这样的方法的魔力。在(67)中,我们将组件保存/包含为动态对象本身,因此我们可以在其上调用(64)中需要的任何方法。
我们将只讨论方法(61),因为在(65)和(66)中适用类似的逻辑。当在DecoratorB中找不到合适的方法名称时调用方法(61) 。在(62)中,借助反射魔法,我们想看看我们的组件是否有这样的名称的方法,如果有,我们在(63)中调用它。如果不是,那么我们看看我们的组件本身是否是 DynamicObject,如果是,我们将调用传递给它的组件(64)。
这是一个聪明的设计,递归地将方法调用传递给包含的组件。我把它留给读者研究一下。
实际结果见(70)。以前不可能的调用现在可以工作了。特别是,在(71)和(72)中,我们调用了包含层次结构,这在经典装饰器中是不可能的。有趣的是(73)对不存在的方法的调用被简单地忽略了。
动态装饰器模式是经典装饰器设计的强大替代方案。但当然,动态对象的使用会给应用程序带来性能成本。
结论
装饰器模式是一种非常流行和重要的模式。它经常用于文件和IO流。顺便提一下,C#自己的IO流库是围绕装饰器模式构建的。
首先,我们查看了经典装饰器模式,该模式在文献和GoF书中都有描述。我们讨论了它存在的问题,特别是需要手动实现通过包含层次结构传递功能所需的所有方法。然后我们查看了泛型装饰器模式版本,它在学术上很有趣,但并没有带来太多新的东西。
最后,我们查看了动态装饰器模式,它是该模式最有效的版本。它是由c#特性DynamicObject实现的,并解决了经典装饰器模式存在的许多问题。
参考
- [1]德米特里·内斯特鲁克:https ://www.udemy.com/course/design-patterns-csharp-dotnet/
- [2]Dmitri Nesteruk:.NET Core 3中的设计模式:C#和F#中面向对象软件设计的可重用方法
https://www.codeproject.com/Articles/5328817/Decorator-Pattern-in-Csharp-3-Versions