65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.11/5 (4投票s)

2016 年 12 月 29 日

CPOL

4分钟阅读

viewsIcon

14765

通过采用 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): LettuceSandwichHamSandwich 是继承自 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) 下许可。

© . All rights reserved.