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

装饰器模式详解

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.67/5 (16投票s)

2016年5月27日

CPOL

3分钟阅读

viewsIcon

18773

downloadIcon

139

本文解释了装饰器模式及其用法。

 

引言

本文解释了装饰器模式,它的用法,并将它与实现相同结果的其他方式进行比较。

背景

定义是这样说的:“装饰器模式是一种设计模式,它允许动态地向单个对象添加行为,而不会影响来自同一类的其他对象的行为。”
它也遵守单一职责原则和开放-封闭原则。

让我们借助代码来理解这个定义。

使用代码

一家电信组织“xyz”雇佣了一位开发人员 **John**,来编写一个应用程序,以 pdf 格式为客户生成账单。John 创建了一个名为“Invoice”的类,其中包含 Generate() 方法。

    public class Invoice
    {
        public PdfDocument Generate()
        {
            // Create a new PDF document
            PdfDocument document = new PdfDocument();

            // Create an empty page
            PdfPage pdfPage = document.AddPage();

            // Get an XGraphics object for drawing
            XGraphics graph = XGraphics.FromPdfPage(pdfPage);

            // Create a font
            XFont font = new XFont("Verdana", 20, XFontStyle.Bold);

            graph.DrawString("This is your bill....", font, 
                XBrushes.Black, new XRect(0, 0, pdfPage.Width.Point, pdfPage.Height.Point), 
                XStringFormats.Center);

            return document;
        }
    }

现在,他的公司表示,如果客户没有支付上个月的账单,应用程序应该在每个账单页面中添加水印内容。水印内容可能类似于“四月和五月账单”或“三月、四月和五月账单”。 他修改了如下代码

1) 在此类中添加一个布尔属性“Watermark”。如果为 true,则添加水印内容,否则根本不添加水印。

2) 添加一个参数化构造函数来接受 Watermark 属性的值。

3) 修改 Generate 方法以基于此属性值添加水印逻辑。

 public class Invoice
    {
        public bool Watermark { get; private set; }
        public Invoice(bool waterMark)
        {
            Watermark = waterMark;
        }

        public PdfDocument Generate()
        {
            // Create a new PDF document
            PdfDocument document = new PdfDocument();

            // Create an empty page
            PdfPage pdfPage = document.AddPage();

            // Get an XGraphics object for drawing
            XGraphics graph = XGraphics.FromPdfPage(pdfPage);

            // Create a font
            XFont font = new XFont("Verdana", 20, XFontStyle.Bold);

            graph.DrawString("This is your bill....", font, XBrushes.Black, new XRect(0, 0, pdfPage.Width.Point, pdfPage.Height.Point), XStringFormats.Center);

            if (Watermark)
            {
                string waterMarkText = "April and May bill.";
                // Get the size (in point) of the text
                XSize size = graph.MeasureString(waterMarkText, font);

                // Define a rotation transformation at the center of the page
                graph.TranslateTransform(pdfPage.Width / 2, pdfPage.Height / 2);
                graph.RotateTransform(-Math.Atan(pdfPage.Height / pdfPage.Width) * 180 / Math.PI);
                graph.TranslateTransform(-pdfPage.Width / 2, -pdfPage.Height / 2);

                // Create a string format
                XStringFormat format = new XStringFormat();
                format.Alignment = XStringAlignment.Near;
                format.LineAlignment = XLineAlignment.Near;

                // Create a dimmed red brush
                XBrush brush = new XSolidBrush(XColor.FromArgb(128, 255, 0, 0));

                // Draw the string
                graph.DrawString(waterMarkText, font, brush,
                  new XPoint((pdfPage.Width - size.Width) / 2, (pdfPage.Height - size.Height) / 2),
                  format);

            }
            return document;
        }
    }

到目前为止,一切都好吗? 没有? 哪里错了? 是的,John 在这里违反了开放-封闭原则。 他修改了现有代码,这可能会在现有功能中引入错误。

在咨询了他的技术架构师后,John 采取了一种子类方法。 他没有触及现有的 Invoice 类,而是创建了它的一个子类,并将其命名为 WatermarkedInvoice。

public class Invoice
    {
        //Other common members used by child classes as well.
        public virtual PdfDocument Generate()
        {
            // Create a new PDF document
            PdfDocument document = new PdfDocument();

            // Create an empty page
            PdfPage pdfPage = document.AddPage();

            // Get an XGraphics object for drawing
            XGraphics graph = XGraphics.FromPdfPage(pdfPage);

            // Create a font
            XFont font = new XFont("Verdana", 20, XFontStyle.Bold);

            graph.DrawString("This is your bill....", font,
              XBrushes.Black, new XRect(0, 0, pdfPage.Width.Point, pdfPage.Height.Point),
              XStringFormats.Center);

            return document;
        }
    }

 

 public class WatermarkedInvoice: Invoice
    {
        public override PdfDocument Generate()
        {
            // Create a new PDF document
            PdfDocument document = new PdfDocument();

            // Create an empty page
            PdfPage pdfPage = document.AddPage();

            // Get an XGraphics object for drawing
            XGraphics graph = XGraphics.FromPdfPage(pdfPage);

            // Create a font
            XFont font = new XFont("Verdana", 20, XFontStyle.Bold);

            graph.DrawString("This is your bill....", font,
              XBrushes.Black, new XRect(0, 0, pdfPage.Width.Point, pdfPage.Height.Point),
              XStringFormats.Center);

            string waterMarkText = "April and May bill.";
            // Get the size (in point) of the text
            XSize size = graph.MeasureString(waterMarkText, font);

            // Define a rotation transformation at the center of the page
            graph.TranslateTransform(pdfPage.Width / 2, pdfPage.Height / 2);
            graph.RotateTransform(-Math.Atan(pdfPage.Height / pdfPage.Width) * 180 / Math.PI);
            graph.TranslateTransform(-pdfPage.Width / 2, -pdfPage.Height / 2);

            // Create a string format
            XStringFormat format = new XStringFormat();
            format.Alignment = XStringAlignment.Near;
            format.LineAlignment = XLineAlignment.Near;

            // Create a dimmed red brush
            XBrush brush = new XSolidBrush(XColor.FromArgb(128, 255, 0, 0));

            // Draw the string
            graph.DrawString(waterMarkText, font, brush,
              new XPoint((pdfPage.Width - size.Width) / 2, (pdfPage.Height - size.Height) / 2),
              format);

            return document;
        }
    }

现在,他的组织表示,如果客户的信用额度已经被超过,应用程序应该显示红色边框。 可能的情况有

1) 客户在一个月内就超过了信用额度。 因此,账单将只有红色边框,但没有水印内容。

2) 客户没有支付上个月的账单,并且在本月超过了信用额度。 账单将同时具有红色边框和水印内容。

3) 客户没有支付上个月的账单,但使用量仍在信用额度内。 账单将仅具有水印内容。

4) 客户已支付上个月的账单,并且本月的使用量在信用额度内。 账单既不会有水印内容,也不会有红色边框。

如果 John 继续使用子类方法,他最终会创建以下类

1) WatermarkedInvoice

2) RedColoredBorderInvoice

3) WatermarkAndRedColoredBorderInvoice

想象一下,如果组织添加更多彼此独立的业务逻辑,并且 John 需要基于此显示任何视觉标记,那么类的数量将会是多少。

 

那么,解决方案是什么? 装饰器模式,因为它 允许动态地向单个对象添加行为,而不会影响其他对象的行为。

 

装饰器模式首先创建/设计一个基类,然后根据需要对其进行装饰。 例如

1) 披萨,它有一个底,它的所有配料,如洋葱、蘑菇、辣椒、番茄都是装饰组件,即装饰器。 您可以使用任意数量的装饰组件来装饰披萨底。

2) 汉堡:一个面包(底)用奶酪、蔬菜、培根等(装饰器)装饰。

类似地,在我们当前的示例中,基本账单可以被认为是基类,而水印或红色边框是装饰器。

为了遵循这种设计模式,John 创建了以下项目

组件 (IInvoice):一个接口,由基类和装饰器实现。

//Component 
public interface IInvoice 
{ 
     PdfDocument Generate(); 
}

 

具体组件 (Invoice):需要装饰的基类。

//Concreate component
  public class Invoice : IInvoice
    {
        public PdfDocument Generate()
        {
            // Create a new PDF document
            PdfDocument document = new PdfDocument();

            // Create an empty page
            PdfPage pdfPage = document.AddPage();

            // Get an XGraphics object for drawing
            XGraphics graph = XGraphics.FromPdfPage(pdfPage);

            // Create a font
            XFont font = new XFont("Verdana", 20, XFontStyle.Bold);

            graph.DrawString("This is your bill....", 
            font, XBrushes.Black, 
            new XRect(0, 0, pdfPage.Width.Point, pdfPage.Height.Point), XStringFormats.Center);

            graph.Dispose();

            return document;
        }
    }

装饰器 (InvoiceDecorator):所有装饰器的基类。

 public abstract class InvoiceDecorator:IInvoice
    {
        protected IInvoice _invoice;

        public InvoiceDecorator(IInvoice invoice)
        {
            _invoice = invoice;
        }
        public virtual PdfDocument Generate()
        {
            PdfDocument invoice=null;
            if (_invoice != null)
                invoice = _invoice.Generate();
            return invoice;
        }
    } 

具体装饰器 A (DecoratedWithWatermark):一个装饰器。

 

public class DecoratedWithWatermark : InvoiceDecorator
    {
        public DecoratedWithWatermark(IInvoice invoice) : base(invoice)
        {
            _invoice = invoice;
        }
        public override PdfDocument Generate()
        {
            PdfDocument invoice = base.Generate();
            invoice = AddWatermark(invoice);
            return invoice;
        }
        private PdfDocument AddWatermark(PdfDocument invoice)
        {
            PdfPage page = invoice.Pages[0];
            string watermark = "April and May bill.";
            XFont font = new XFont("Verdana", 20, XFontStyle.Bold);
            // Get an XGraphics object for drawing beneath the existing content
            XGraphics gfx = XGraphics.FromPdfPage(page, XGraphicsPdfPageOptions.Prepend);

            // Get the size (in point) of the text
            XSize size = gfx.MeasureString(watermark, font);

            // Define a rotation transformation at the center of the page
            gfx.TranslateTransform(page.Width / 2, page.Height / 2);
            gfx.RotateTransform(-Math.Atan(page.Height / page.Width) * 180 / Math.PI);
            gfx.TranslateTransform(-page.Width / 2, -page.Height / 2);

            // Create a string format
            XStringFormat format = new XStringFormat();
            format.Alignment = XStringAlignment.Near;
            format.LineAlignment = XLineAlignment.Near;

            // Create a dimmed red brush
            XBrush brush = new XSolidBrush(XColor.FromArgb(128, 255, 0, 0));

            // Draw the string
            gfx.DrawString(watermark, font, brush,
              new XPoint((page.Width - size.Width) / 2, (page.Height - size.Height) / 2),
              format);

            gfx.Dispose();

            return invoice;
        }
    }

具体装饰器 B (DecoratedWithRedColoredBorder):另一个装饰器

 

public class DecoratedWithRedColoredBorder : InvoiceDecorator
    {
        public DecoratedWithRedColoredBorder(IInvoice invoice) : base(invoice)
        {
            _invoice = invoice;
        }
        public override PdfDocument Generate()
        {
            PdfDocument invoice = base.Generate();
            invoice = AddBorder(invoice);
            return invoice;
        }
        private PdfDocument AddBorder(PdfDocument report)
        {
            PdfPage page = report.Pages[0];
            XFont font = new XFont("Verdana", 20, XFontStyle.Bold);

            // Get an XGraphics object for drawing beneath the existing content
            XGraphics gfx = XGraphics.FromPdfPage(page, XGraphicsPdfPageOptions.Prepend);

            // Adds a border

            XPen pen = new XPen(XColors.Red, Math.PI);
            
            gfx.DrawRectangle(pen, XBrushes.White, 10, 10, page.Width - 20, page.Height - 20);

            gfx.Dispose();
            return report;
        }
    }

通过这样做,John 能够动态地添加行为,例如独立地添加水印和红色边框。

客户端类如下所示

首先创建 invoice 对象,然后根据用户输入将此对象注入到装饰后的对象中。

UI 有 2 个复选框,一个用于水印,另一个用于红色边框。 根据用户的选择,基本 invoice 将相应地进行装饰。

  IInvoice invoice = new DecoratorPattern.UsingDecoratorPattern.Invoice();
            foreach (var checkedItem in checkedListBox1.CheckedItems)
            {
                if (String.Compare(checkedItem.ToString(), "Watermark", true) == 0)
                {
                    invoice = new DecoratedWithWatermark(invoice);
                }
                if (String.Compare(checkedItem.ToString(), "Border", true) == 0)
                {
                    invoice = new DecoratedWithRedColoredBorder(invoice);
                }
            }

            PdfDocument document= invoice.Generate();
            string pdfFilename = "DecoratedDesignPatternExplained.pdf";
            string path = Path.Combine(
                          System.IO.Path.GetDirectoryName(
                             System.Reflection.Assembly.GetEntryAssembly().Location)
                             , pdfFilename
                             );
            document.Save(path);
            this.textBox1.Text = String.Format("{0}: PDF generated at {1}", DateTime.Now.ToShortTimeString(), path);

我希望这个例子展示了装饰器模式在这种情况下有多强大。

欢迎您的评论/反馈。 让我们一起学习成长。

构建项目时,请确保已添加 pdfsharp 库的引用。

历史

First version.

© . All rights reserved.