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

用实际软件编程示例解释 C# 中的装饰器模式

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.28/5 (11投票s)

2020 年 4 月 10 日

CPOL

3分钟阅读

viewsIcon

11070

downloadIcon

121

本文通过一个实际的软件编程示例演示了 C# 中装饰器模式的用法。

背景

GoF 定义:动态地给一个对象添加一些额外的职责。就增加功能来说,装饰器模式比生成子类实现更为灵活。

此模式支持 SOLID 的开放/封闭原则,这意味着您可以在不修改现有类的情况下向其添加新功能。您可以使用继承组合向现有类添加新功能。

让我们考虑一个实际的软件编程示例。

我的应用程序中有一个发票模块,用于打印简单的客户发票。此发票包含文本数据,如客户姓名、地址、订单号、订单金额和订单日期。多年来,我的许多客户都使用此功能。

现在,我的一个客户(客户 A)希望打印带有颜色的发票。第二个客户(客户 B)希望打印带有标题的发票。第三个客户(客户 C)希望打印带有页脚的发票。那么,您认为我该如何满足这一需求?继承?让我们试试。

Using Inheritance

通过继承,我将得到上面的类。我将为每个新功能创建一个子类。到目前为止似乎还不错。但是,当一个新客户(客户 D)希望同时打印带有标题和页脚的发票时,上述场景将变得复杂。如果我们想实现客户 D 的要求,我们的结构可能如下所示

Multiple Inheritance

等等,这是多重继承。我在 C# 中不能这样做。我无法从以上两个类实现。因此,现在我们将不得不创建一个新的子类,它将打印带有标题和页脚的发票,并满足客户 D 的要求。

Subclassing

上述子类方法存在两个问题

  1. 你最终会得到很多子类。确切地说,每个组合都有一个子类,例如标题 + 页脚是一个组合。另一个组合可以是颜色 + 标题...等等。在大型系统中,维护和调试大量子类将很困难。
  2. 如果您遵循 SOLID 的单一职责原则,它指出一个类应该只处理一个功能部分。因此,我们的子类不应处理组合任务。 这意味着一个类应该添加颜色,而另一个类应该添加标题信息。

为了克服上述问题,使用了装饰器模式。

使用装饰器模式,您可以在不影响现有类的情况下附加新功能。它为扩展现有类提供了一种替代子类化的方法。

Decorator pattern class diagram

在上面的示例中,InvoiceBaseInvoice 类是现有类。我需要添加的任何新功能都将是一个 Decorator。我们可以将多个装饰器附加到我们的现有类,而无需为每个组合创建单独的子类。我创建了一个 abstractInvoiceDecorator 和三个额外的类,ColorDecoratorHeaderDecoratorFooterDecoratorInvoiceDecorator 具有 InvoiceBase 对象的组合。当我们想向现有功能添加新功能时,我们使用 AttachTo 方法。这样做的想法是在将来添加一个新的装饰器,而不会影响我现有的 Invoice 类。

我创建了一个示例应用程序,模拟发票打印操作。

Using the Code

以下是我的现有类,在添加新功能时不会修改它们。

    // This is the base abstract class which is inherited 
    // by all concrete and decorator classes
    abstract class InvoiceBase
    {
        // string data stores the content which it to be printed in the invoice.
        // This variable will be altered by the subclasses. Thus, it is marked protected.
        protected static string data;
        
        // This function will be implemented in all the concrete classes as well as decorators
        public abstract void CreateInvoice();

        public void PrintInvoice()
        {
            Console.WriteLine(data);
        }

        //This function clear the variable value after invoice is printed.
        public void Reset()
        {
            data = string.Empty;
        }
    }

    public class Invoice : InvoiceBase
    {
        public override void CreateInvoice()
        {
            data += "\n";
            data += "\tCustomer Name : Chris\n";
            data += "\tCustomer Address : Edmond Road, NJ\n";
            data += "\tOrder No : 1254158\n";
            data += "\tOrder Amount : Rs. 2540/- \n";
            data += "\tOrder Date : 09-Apr-2020 \n";
            data += "\n";
        }
    }

以下是与具体实现解耦的 Decorator 类。我们可以添加任意数量的装饰器来扩展功能。另请注意,我按照 SOLID 原则为一个职责创建了一个类,即颜色处理由 ColorDecorater 负责,而标题信息处理由 HeaderDecorator 类负责。

    // This is the base Decorator class which has the composition, i.e., InvoiceBase object
    // AttachTo method is used to attach responsibility dynamically through client code.
    abstract class InvoiceDecorator : InvoiceBase
    {
        InvoiceBase invoiceBase;
        public void AttachTo(InvoiceBase invoice)
        {
            this.invoiceBase = invoice;
        }
        public override void CreateInvoice()
        {
            invoiceBase.CreateInvoice();
        }
    }

    //These are individual decorator classes.
    class ColorDecorator : InvoiceDecorator
    {
        public override void CreateInvoice()
        {
            AddColor();
            base.CreateInvoice();
        }
        private void AddColor()
        {
            Console.ForegroundColor = ConsoleColor.Green;
        }
    }

    class HeaderDecorator : InvoiceDecorator
    {
        public override void CreateInvoice()
        {
            AddHeader();
            base.CreateInvoice();
        }
        private void AddHeader()
        {
            string header = "\n\tBlue Heaven Inc.\n"
                            + "\tBay Area, NC\n"
                            + "\t+1 784251485\n\n";
            data = header + data;            
        }
    }

    class FooterDecorator : InvoiceDecorator
    {
        public override void CreateInvoice()
        {
            base.CreateInvoice();
            AddFooter();
        }
        void AddFooter()
        {
            string footer = "\n\tCopyright @ 2020.All rights reserved\n";
            data += footer;
        }
    }

客户端代码如下所示

    // CASE 1: This is the existing implementation to print a simple invoice.
    InvoiceBase invoice = new Invoice();
    invoice.CreateInvoice();
    invoice.PrintInvoice();

    // CASE 2: Add color to the invoice
    InvoiceBase invoice = new Invoice();    
    InvoiceDecorator colorDecorator = new ColorDecorator();

    colorDecorator.AttachTo(invoice);
    colorDecorator.CreateInvoice();

    invoice.PrintInvoice();

    // CASE 3: Add Color, Header and Footer to the invoice
    InvoiceBase invoice = new Invoice();    
    InvoiceDecorator colorDecorator = new ColorDecorator();
    InvoiceDecorator headerDecorator = new HeaderDecorator();
    InvoiceDecorator footerDecorator = new FooterDecorator();

    colorDecorator.AttachTo(invoice);
    footerDecorator.AttachTo(colorDecorator);
    headerDecorator.AttachTo(footerDecorator);
    headerDecorator.CreateInvoice();

    invoice.PrintInvoice();

关注点

很多时候,在处理客户需求时,您没有完整的需求,而是增量开发。此模式可用于此类场景。您可以根据新需求添加装饰器,从而确保您的基类首先不复杂。

历史

  • 2020 年 4 月 10 日 - 初始版本
© . All rights reserved.