在软件开发中,经常面临需要为现有系统添加新功能的需求。传统的继承方法虽然能够实现功能的扩展,但随着功能的增加,继承结构会变得复杂且难以维护。为了解决这个问题,装饰者模式提供了一种灵活的替代方案。装饰者模式遵循SOLID原则中的开闭原则,即在不修改现有类的基础上,为现有类添加新功能。
以一个实际的软件开发示例来说明。假设有一个发票模块,它打印一个简单的客户发票。这个发票包含客户名称、地址、订单号、订单金额和订单日期等文本数据。这个功能多年来被许多客户使用。现在,一个客户(客户A)想要打印带有颜色的发票。第二个客户(客户B)想要打印带有标题的发票。第三个客户(客户C)想要打印带有页脚的发票。那么,应该如何满足这些需求呢?
如果使用继承,最终会得到上述类。对于每个新功能,都会创建一个子类。目前看来似乎还可以。但是,当一个新的客户(客户D)想要同时打印带有标题和页脚的发票时,情况就会变得复杂。如果想要实现客户D的需求,结构可能看起来像这样:
哦,等等,这是多重继承。在C#中,不能这样做。不能从上述两个类中实现。所以现在,将不得不创建一个新的子类,它将打印带有标题和页脚的发票,并满足客户D的需求。
上述子类方法存在两个问题:
1. 会得到很多子类。确切地说,每个组合,如标题+页脚是一个组合。另一个组合可以是颜色+标题……等等。在大型系统中,维护和调试大量的子类将变得困难。
2. 如果遵循SOLID原则中的单一职责原则,它指出一个类应该处理单一部分功能。这意味着子类不应该处理任务的组合。这意味着一个类应该添加颜色,而另一个类应该添加标题信息。
为了克服上述问题,使用装饰者模式。使用装饰者模式,可以在不影响现有类的情况下附加新功能。它为扩展现有类提供了一种替代继承的方法。
在上面的例子中,InvoiceBase和Invoice类是现有类。需要添加的任何新功能都将是一个装饰者。可以将多个装饰者附加到现有类上,而不需要为每个组合创建单独的子类。创建了一个抽象类InvoiceDecorator和三个额外的类,ColorDecorator、HeaderDecorator和FooterDecorator。
InvoiceDecorator有一个InvoiceBase对象的组合。当想要向现有功能添加新功能时,使用AttachTo方法。这个想法是在不影响现有Invoice类的情况下,将来添加新的装饰者。
创建了一个示例应用程序,模拟了发票打印操作。
以下是现有类,它们在添加新功能时不会被修改。
public abstract class InvoiceBase
{
protected static string data;
public abstract void CreateInvoice();
public void PrintInvoice()
{
Console.WriteLine(data);
}
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";
}
}
以下是装饰者类,它们与具体实现解耦。可以添加任意数量的装饰者来扩展功能。同时,请注意,创建了一个类来处理一个责任,根据SOLID原则,即颜色处理由ColorDecorator类处理,标题信息处理由HeaderDecorator类处理。
public abstract class InvoiceDecorator : InvoiceBase
{
InvoiceBase invoiceBase;
public void AttachTo(InvoiceBase invoice)
{
this.invoiceBase = invoice;
}
public override void CreateInvoice()
{
invoiceBase.CreateInvoice();
}
}
public class ColorDecorator : InvoiceDecorator
{
public override void CreateInvoice()
{
AddColor();
base.CreateInvoice();
}
private void AddColor()
{
Console.ForegroundColor = ConsoleColor.Green;
}
}
public 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;
}
}
public 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();