C# 中的装饰器模式 - 3 个版本
C# 中装饰器模式的教程,展示了 3 个 C# 版本
引言
装饰器模式是一种迷人且非常流行的模式。该模式的目的是动态地为现有对象添加附加功能。装饰器提供了一种替代继承的方式来扩展功能。虽然可以通过继承对象类来为整个类对象添加功能,但装饰器模式旨在仅为单个对象添加功能,而保持该类的其他对象不变。
有人评论说,通过增强现有类型而不修改原始类型,该模式遵循开闭原则。
非常有趣的是,该模式通过“错误”的方式实现其目标,违背了 OO 的建议。
组合与继承:如何选择?
在 OOA/OOD 课程中,一个典型的问题是,对于给定的类/对象 A
,在创建一个新类/对象 B
以重用类/对象 A
时,如何以及根据什么标准在组合和继承之间进行选择?通常的建议是:如果您打算重用现有类 A
的 Public
接口,请使用继承。如果您打算仅重用现有类 A
的功能,请选择组合。
因此,装饰器模式恰恰相反。它打算重用现有类的 Public
接口,但选择了组合。这个决定对扩展 Public
接口的问题产生了一些影响,我们稍后会看到。
经典装饰器模式 - 版本 1
经典装饰器是 GoF 书中提供的模式版本,并经常在文献中提及。通常,您会有一个抽象某个真实类 ComponentA
的接口 IComponent
。我们的愿望是用一个提供更多/增强功能的对象来替换 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
的 public
接口(5)、(8),但此外,它还在(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
类中要注意的关键是构造函数(14)如何将传入的 IComponent
分配给一个 private
属性(13)。这就是我们所说的“组合”,装饰器的所有魔力都源于此。
Decorator
(10)现在能够完全重用(13)中包含对象的功能,并公开自己的接口,在该接口中,它可以自由地重用或修改(13)对象的功能。
在(15)中的方法 MethodI1
中,我们看到 DecoratorB
如何提供自己的接口(10)实现,并在该过程中重用包含对象(13)的 MethodI1
。
除了(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
重用。它通常通过以下两种方式规避:
- 在(24)中将
ComponentA
传递给DecoratorB
之前进行配置 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 语言中不可行。
我不知道是否有人独立发表过类似的版本,但当我阅读关于动态代理的文章时,我得到了它的灵感。我想到了可以使用类似的技术来克服经典装饰器模式的局限性。
关键思想是拦截对未知方法的调用,并将调用重定向到包含的对象进行处理。然后,如果包含的对象无法解析方法调用,它将递归地将方法调用传递给它的包含对象进行解析,等等。我们想强调的是,这里的关键技巧是将方法调用传递给包含层次结构,而不是继承层次结构。
此处显示的 code 是演示概念级别的质量,用于生产使用可能需要一些改进。
我们首先展示类图和代码,然后进行讨论。
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] Dmitri Nesteruk:https://www.udemy.com/course/design-patterns-csharp-dotnet/
- [2] Dmitri Nesteruk:.NET Core 3 中的设计模式:C# 和 F# 中面向对象软件设计的可重用方法
历史
- 2022 年 4 月 1 日:初始版本