装饰器设计模式 - Lambda 表达式重构






4.11/5 (4投票s)
通过采用 lambda 闭包方法进行的装饰器设计模式的修订。
引言
装饰器设计模式是一种提供替代继承的成熟方法。虽然继承在编译时添加行为,并且添加的操作会影响原始类的所有实例;使用装饰器,可以在运行时根据实际使用情况,为特定对象添加额外功能,而不影响其他对象。展望未来,下一步可能是重新审视该模式的实现。在此模式中,可以在运行时添加或删除职责。为此,我将采用装饰器模式,并使用 lambda 表达式的简写形式来实现它,这允许您在要返回装饰类的相同位置编写新行为。
背景
我们将以一个简单虚构的装饰器模式示例:描述一个典型的三明治组装。对于一个经典且已知的现实世界示例,您也可以看看该模式是如何在 Java 和 .NET Framework 的 **I/O Stream** 实现中融入的。
使用代码
该模式的设计目的是将多个装饰器捆绑在一起,以便在覆盖一个或多个方法时添加新功能。装饰器设计模式是一种结构型设计模式。它也称为包装器。从根本上说,该模式预设了两个组件,第一个是抽象的,第二个是具体的。
首先,我们有定义我们组件的接口
public interface ISandwich
{
string Assemble();
}
实现接口的是一个具体组件
public class BasicSandwich: ISandwich
{
public string Assemble()
{
return "Basic sandwich";
}
}
然后我们决定以不同的方式装饰我们的组件。而这正是装饰器模式的神奇之处。我们可以在不改变现有对象结构的情况下为其添加新功能。
这是用作所有组件装饰器基类的类
public abstract class SandwichDecorator: ISandwich
{
protected ISandwich _sandwich;
public SandwichDecorator(ISandwich sandwich)
{
_sandwich = sandwich;
}
public virtual string Assemble()
{
return _sandwich.Assemble();
}
}
这是一对继承自 Decorator 类的类,为组件提供了一个装饰器
public class LettuceSandwich: SandwichDecorator
{
public LettuceSandwich(ISandwich sandwich): base(sandwich)
{
}
public override string Assemble()
{
return base.Assemble() + " with lettuce";
}
}
public class HamSandwich: SandwichDecorator
{
public HamSandwich(ISandwich sandwich): base(sandwich)
{
}
public override string Assemble()
{
return base.Assemble() + " with ham";
}
}
最后,这是使用新描述符类的方式
ISandwich sandwich = new LettuceSandwich(new BasicSandwich());
string finishedSandwich = sandwich.Assemble(); // Basic Sandwich with lettuce
sandwich = new LettuceSandwich(new HamSandwich(new BasicSandwich()));
finishedSandwich = sandwich.Assemble(); // Basic Sandwich with ham with lettuce
同样,在 .NET 中,BufferedStream
用缓冲能力装饰或包装另一个流。缓冲通过减少与后备存储的往返次数来提高性能。例如,这是我们如何用 20 KB 的 BufferedStream
包装 FileStream
// Write 100K to a file:
File.WriteAllBytes ("myFile.bin", new byte [100000]);
using (FileStream fs = File.OpenRead ("myFile.bin"))
using (BufferedStream bs = new BufferedStream (fs, 20000))
{
bs.ReadByte();
Console.WriteLine (fs.Position); // 20000
}
描述符模式的整个结构图可以一目了然地总结如下。
组件 (Component): ISandwich
是一个接口,包含将被 ConcreteClass 和 Decorator 实现的成员。
具体组件 (ConcreteComponent): BasicSandwich
是一个实现 Component 接口的类。
装饰器 (Decorator): SandwichDecorator
是一个抽象类,它实现 Component 接口并包含对 Component 实例的引用。此类也用作所有组件装饰器的基类。
具体装饰器 (ConcreteDecorator): LettuceSandwich
和 HamSandwich
是继承自 Decorator 类的类,为组件提供装饰器。
现在让我们看看如何使用 lambda 来实现同样的功能,而不是声明一个提供装饰模板的 abstract
类,我们将创建一个装饰器,该装饰器要求用户提供具有 Func<ISandwich, ISandwich>
签名的函数,用于装饰组件。
public static class SandwichDecorator
{
public static ISandwich Decorate(ISandwich sandwich, params Func<ISandwich, ISandwich>[] funcs)
{
ISandwich res = sandwich;
foreach (Func<ISandwich, ISandwich> f in funcs)
{
res = f.Invoke(res);
}
return res;
}
}
好的,现在我们可以在哪里定义我们的装饰呢?您可以将它们添加为 Decorator 类中的静态方法,甚至可以在 ISandwich
接口中通过编写两个扩展方法来实现。
public class SandwichFactory
{
private sealed class SandwichImpl : ISandwich
{
internal Func<string> _assemble = null;
public string Assemble()
{
return _assemble();
}
}
public ISandwich Create() {
return new SandwichImpl() { _assemble = () => String.Empty };
}
public ISandwich Create(Func<string> bakeFunc) {
return new SandwichImpl() { _assemble = bakeFunc };
}
}
public static class SandwichDecorators
{
public static ISandwich WithLettuce(this ISandwich @this, ISandwich sandwich = null)
{
return (new SandwichFactory()).Create(() => (sandwich ?? @this).Assemble() + " with lettuce");
}
public static ISandwich WithHam(this ISandwich @this, ISandwich sandwich = null)
{
return (new SandwichFactory()).Create(() => (sandwich ?? @this).Assemble() + " with ham");
}
}
您可能会注意到,我使用了一个工厂模式来创建一个新的 ISandwich
对象实例,这种方法是为了解决一个已知的 C# 限制而必需的,并且这个属于每个面向对象开发者词汇表中的基本模式,以一种非常优雅的方式帮助我们应对它。
批评者认为装饰器模式使用了大量相似类型的对象。为了减少样板代码,以下是构建要应用的装饰链的行。
return (new SandwichFactory()).Create(() => (sandwich ?? @this).Assemble() + " with lettuce");
return (new SandwichFactory()).Create(() => (sandwich ?? @this).Assemble() + " with ham");
这两个 lambda 用作 SandwichFactory
类中的 Create
方法的参数。
现在,这就是这个模式的使用方式。
var mySandwich = new BasicSandwich();
var mySandwich2 = mySandwich.WithLettuce();
var mySandwich3 = mySandwich.WithHam(mySandwich);
var mySandwich4 = SandwichDecorator.Decorate(mySandwich, mySandwich.WithLettuce, mySandwich.WithHam);
Console.WriteLine(mySandwich.Assemble()); // Basic sandwich
Console.WriteLine(mySandwich2.Assemble()); // Basic sandwich with lettuce
Console.WriteLine(mySandwich3.Assemble()); // Basic sandwich with ham
Console.WriteLine(mySandwich4.Assemble()); // Basic sandwich with lettuce with ham
关注点
您可以看到,代码变得更加清晰和简洁,并且我们没有使用继承来构建我们的装饰器。
这只是众多可以使用 lambda 表达式改进的设计模式之一。还有更多函数式特性可以用来改进解决方案,以及许多其他已知的模式;在 C# 中,函数式编程与面向对象编程相结合,您可以将函数对象用作任何其他对象类型。
我希望您在设计软件时能享受装饰器模式。您对本文的宝贵反馈、问题或评论始终受到欢迎。
参考文献
本文包含的虚构装饰器示例的灵感来源于以下文章:Gang of Four – 使用装饰器模式进行装饰
许可证
本文以及任何相关的源代码和文件,均在The Code Project 开放许可 (CPOL) 下许可。