装饰者(Decorator)
1.1.1 摘要
装饰者模式:动态地给一个对象添加一些额外的职责,就增加功能来说,Decorator模式比生成子类更为灵活。
Decorator模式的工作原理是:可以创建始于Decorator对象(负责新的功能的对象)终于原对象的一个对象“链”。
图1装饰者链
装饰者模式隐含的是通过一条条装饰链去实现具体对象,每一条装饰链都始于一个Componet对象,每个装饰者对象后面紧跟着另一个装饰者对象,而对象链终于ConcreteComponet对象。
关键字:提高内聚性,面向接口编程,封装变化使用组合,OCP
使用率: medium
图2装饰者模式
1.1.2参与类或接口作用
ConcreteComponent:让Decorator对象为自己添加功能。有时候使用ConcreteComponent的派生类提供核心功能,在这种情况就是用ConcreteComponent替代了Component的功能,而且装饰者是继承于ConcreteComponent的子类。
Component:定义ConcreteComponent和Decorator类要实现的方法,简单来说如果一个类继承于该类就具有装饰或被装饰能力。
Decorator:具有特定装饰功能的类,用来装饰ConcreteComponent类。
1.1.3装饰者的实现
装饰者模式顾名思义就是用来装饰物件的,其实像我们生活中礼品的装饰就是将一个礼物包裹的更加赏心悦目,更符合要送给对象和心意。OK现在想想更加接近编程的例子,我想大家都了解IP报文,IP报文是通过很多报头组成的,而且每个报头都有其作用(例如:数据包的目的地址和源地址)。每次当我们要传输数据的时候,我们电脑将根据TCP/IP报文格式去装饰我们要传输的数据,好啦装饰者模式出现啦。
图3装饰者实现
现在我们定义了IPMsg类,它是我们要发送的数据在发送过程我们可以随意添加报头(注意:IP报文格式是固定),但数据本身毫不关心究竟要添加什么报头和怎样添加,SourAddress和DestAddress是我们的装饰者负责给我们数据添加报头,装饰者只关心自己添加的功能,并不关心其他装饰者的实现。
Decorator模式帮助我们将问题分为两部分:
-
如何实现提供新功能对象。
-
如何为每种特殊情况组织对象。
通过上面的UML图我们可以发现,和我们前面介绍装饰者模式结构是一模一样,但我们实现设计模式的过程要重视的不是模式而是设计原则。
/// <summary>
/// 抽象的Component。
/// </summary>
abstract public class Component
{
abstract public void addMessage();
}
abstract public class MessageDecorator : Component
{
// 为了实现简单,并没有使用属性
private Component component;
public MessageDecorator()
{
}
public MessageDecorator(Component component)
{
this .component = component;
}
}
public class IPMsg : Component
{
public override void addMessage()
{
/// /要被装饰数据。
Console.Write( " Data-> " );
}
}
/// <summary>
/// 装饰者
/// </summary>
public class SourAddress : MessageDecorator
{
private Component component;
public SourAddress(Component component)
{
this .component = component;
}
public override void addMessage()
{
this .component.addMessage();
Console.Write( " SourAddress(Decorator)-> " );
}
}
/// <summary>
/// 装饰者
/// </summary>
public class DestAddress : MessageDecorator
{
private Component component;
public DestAddress(Component component)
{
this .component = component;
}
public override void addMessage()
{
this .component.addMessage();
Console.Write( " DestAddress(Decorator)-> " );
}
}
为了简单描述出装饰者模式基本结构,我按照装饰者模式定义去实现,但在现实的编程中并不是我们每次实现装饰者模式都应该按照以上结构,往往没有按照以上的结构来实现,而是根据实际情况做出改变,OK接下来我们介绍一个变形的装饰者模式。
1.1.4控件中的装饰者
我相信大家都有用过PS,而且都了解过PS中图层的原理,当我们P图片时候使用图层可以实现很多效果,而且如果我们觉得其中一个图层效果不好看我们可以直接删除,而不影响其他图层,我们每次P出来的照片都是一层层图层的叠加得到的。OK我们再见装饰者模式啦。
通过一个简单的WinForm例子的实现给图片加边框和打标签的程序来介绍一下装饰者模式的变形。
图4图层中的装饰者
现在我们装饰者模式发生了变化,原来的Component类被ConcreteComponent类替代了,而且装饰者成为了ConcreteComponent的子类。也许有人问这还是装饰者模式吗?这不是简单的继承吗?其实我觉得模式的实现不能给那些条条框框给限制住了,更重要的是具体设计具体模式嘛。
/// <summary>
/// ConcreteComponent.
/// </summary>
public partial class Photo : Form
{
protected Image image;
protected Point point;
/// <summary>
/// Initializes a new instance of the <see cref="Photo"/> class.
/// </summary>
public Photo()
{
InitializeComponent();
this .image = new Bitmap( " Rush.jpg " );
this .Text = " DecoratorDemo " ;
this .Height = 200 ;
this .Width = 300 ;
this .Paint += new PaintEventHandler(CustomDrawer);
this .point.X = this .Width / 2 - this .image.Width / 2 ;
this .point.Y = this .Height / 2 - this .image.Height;
}
/// <summary>
/// Customs the drawer.
/// </summary>
/// <param name="sender"> The sender. </param>
/// <param name="e"> The <see cref="System.Windows.Forms.PaintEventArgs"/> instance containing the event data. </param>
public virtual void CustomDrawer( object sender, PaintEventArgs e)
{
e.Graphics.DrawImage( this .image, this .point.X, this .point.Y);
}
}
我们先定义一个Photo类,它继承于Form然后添加虚方法CustomDrawer,每当发生重绘时候都调用CustomDrawer方法,该方法给Form添加一张图片,Photo类并不关心它如何被装饰和如何实现装饰,它只管把图片显示出来就好了。
/// <summary>
/// The decorator.
/// </summary>
public class Bordered : Photo
{
protected Photo photo;
private Color color;
private int borderSize;
public Bordered()
{
}
/// <summary>
/// Initializes a new instance of the <see cref="Bordered"/> class.
/// </summary>
/// <param name="photo"> The photo. </param>
/// <param name="color"> The color. </param>
/// <param name="borderSize"> Size of the border. </param>
public Bordered(Photo photo, Color color, int borderSize)
{
this .photo = photo;
this .color = color;
this .borderSize = borderSize;
this .point.X -= this .borderSize;
this .point.Y -= this .borderSize;
}
/// <summary>
/// Customs the drawer.
/// </summary>
/// <param name="sender"> The sender. </param>
/// <param name="e"> The <see cref="System.Windows.Forms.PaintEventArgs"/> instance containing the event data. </param>
public override void CustomDrawer( object sender, PaintEventArgs e)
{
photo.CustomDrawer(sender, e);
e.Graphics.DrawRectangle( new Pen( this .color, this .borderSize), this .point.X, this .point.Y, this .image.Width, this .image.Height);
}
}
/// <summary>
/// The decorator.
/// </summary>
class Tagged : Photo
{
Photo photo;
string tag;
int number;
public Tagged()
{
}
/// <summary>
/// Initializes a new instance of the <see cref="Tagged"/> class.
/// </summary>
/// <param name="photo"> The photo. </param>
/// <param name="tag"> The tag. </param>
public Tagged(Photo photo, string tag)
{
this .photo = photo;
this .tag = tag;
}
/// <summary>
/// Customs the drawer.
/// </summary>
/// <param name="sender"> The sender. </param>
/// <param name="e"> The <see cref="System.Windows.Forms.PaintEventArgs"/> instance containing the event data. </param>
public override void CustomDrawer( object sender, PaintEventArgs e)
{
photo.CustomDrawer(sender, e);
Random random = new Random();
e.Graphics.DrawString( this .tag, new Font( " Arial " , 16 ),
new SolidBrush(Color.Goldenrod), new PointF(random.Next( this .photo.Width), random.Next( this .photo.Height)));
}
}
然后我们分别定义两个装饰者类Bordered和Tagged,它通过重写CustomDrawer方法给照片添加一个框和标签,这就是装饰的实现,而且它们只关心自己装饰的实现从而提高了装饰类的内聚性。
/// /客户端实现
/// /只用标签装饰
photo = new Tagged(photo, this .txtTag.Text.Trim());
/// /只用照片框装饰
photo = new Bordered(photo, Color.Blue, Convert.ToInt32( this .comBorderSize.Text));
/// /先用照片框再用标签装饰
bordered = new Bordered(photo, Color.Blue, Convert.ToInt32( this .comBorderSize.Text));
photo = new Tagged(bordered, this .txtTag.Text.Trim());
图5客户端UI
通过上面的客户端UI发现,客户端负责的主要职能是使用哪些装饰过程去装饰我们的照片,而装饰者只负责自己的装饰功能提高它的内聚性。其实在.NET3.0中的控件类System.Windows.Controls就是用了装饰者模式实现BorderStyle或ViewBox等效果。
图6装饰效果
图7组合装饰效果
图8 .NET控件中装饰者模式
1.1.5 I/O中的装饰者
通过上面的例子可以发现装饰者模式在UI的动态显示效果的作用,而且把要装饰过程都分配给客户端实现,装饰者只关心自己的装饰功能。而大部分介绍装饰者模式主要以I/O设计为例子。
没错装饰者模式的一个常见应用场合是I/O API的设计,这里我们以.NET中的I/O设计为例,现在让我们看一下.NET中的I/O类。
System.IO.Stream
System.IO.BufferedStream
System.IO.FileStream
System.IO.MemoryStream
System.Net.Sockets.NetworkStream
System.Security.Cryptography.CryptoStream
图9 .NET I/O设计
在学习设计模式之后能更加清晰理解I/O类之间的关系和作用,我讲一下我对I/O Stream的理解:数据就像流水一样在流动,而我们充当着管道工人的工作,要通过使用不同种类和数量不一的管子(不同I/O类)把水输送到千家万户。
现在我们通过一个简单的数据加密来介绍I/O中的装饰者模式。
首先分别定义输入输出流,然后我们再定义一个数据加密流,接着我们对输出流进行装饰(加密)。
fin = new FileStream(inFile, FileMode.Open, FileAccess.Read);
fout = new FileStream(encFile,FileMode.OpenOrCreate, FileAccess.Write);
CryptoStream fenc = new CryptoStream(fout, des3.CreateEncryptor(key, IV),CryptoStreamMode.Write);
图10 明文加密成功
/// /以下是完整的加密代码
class Program
{
public static readonly int MAXSIZE = 100 ;
/// <summary>
/// Mains the specified args.
/// </summary>
/// <param name="args"> The args. </param>
static void Main( string [] args)
{
/// /Get fully qualified file names.
/// /Due to we have only one Module, we can also use "GetModule" method.
string path = Path.GetDirectoryName(Assembly.GetExecutingAssembly().GetModules()[ 0 ].FullyQualifiedName);
path = path.Substring( 0 , path.IndexOf( @" \bin " ) + 1 );
string inFile = path + " InFile.txt " ;
string outFile = path + " UndecoratedOutFile.txt " ;
string encFile = path + " DecoratedOutFile.txt " ;
string decFile = path + " DecoratorDecFile.txt " ;
FileStream fin = new FileStream(inFile, FileMode.Open, FileAccess.Read);
FileStream fout = new FileStream(outFile, FileMode.OpenOrCreate, FileAccess.Write);
fout.SetLength( 0 );
int readLent = 0 ;
long totLen = fin.Length;
byte [] bin = new byte [MAXSIZE];
int len = 0 ;
/// / Read and write the txt file operation.
while (readLent < totLen)
{
len = fin.Read(bin, 0 , MAXSIZE);
fout.Write(bin, 0 , MAXSIZE);
readLent += len;
}
fout.Close();
fin.Close();
Console.WriteLine( " Created undecorated file.\n " );
fin = new FileStream(inFile, FileMode.Open, FileAccess.Read);
fout = new FileStream(encFile,FileMode.OpenOrCreate, FileAccess.Write);
fout.SetLength( 0 );
TripleDESCryptoServiceProvider des3 = new TripleDESCryptoServiceProvider();
byte [] key = HexToBytes( " EA81AA1D5FC1EC53E84F30AA746139EEBAFF8A9B76638895 " );
byte [] IV = HexToBytes( " 87AF7EA221F3FFF5 " );
/// / Use "CryptoStream" to decorate our output stream.
Console.WriteLine( " Now we decorate output stream with CryptoStream...\n " );
/// / Stream was decorated by CryptoStream.
CryptoStream fenc = new CryptoStream(
fout, des3.CreateEncryptor(key, IV), CryptoStreamMode.Write);
readLent = 0 ;
while (readLent < totLen)
{
len = fin.Read(bin, 0 , MAXSIZE);
fenc.Write(bin, 0 , MAXSIZE);
readLent += len;
}
fin.Close();
fout.Close();
// fenc.Close();
Console.WriteLine( " Created decorated file.\n " );
Console.ReadKey();
}
public static byte [] HexToBytes( string hex)
{
byte [] bytes = new byte [hex.Length / 2 ];
for ( int i = 0 ; i < hex.Length / 2 ; i ++ )
{
string code = hex.Substring(i * 2 , 2 );
bytes[i] = byte .Parse(code, System.Globalization.NumberStyles.HexNumber);
}
return bytes;
}
1.1.6 总结
装饰者模式的应用场景:
1、 想透明并且动态地给对象增加新的职责的时候。
2、 给对象增加的职责,在未来存在增加或减少可能。
3、 用继承扩展功能不太现实的情况下,应该考虑用组合的方式。
装饰者模式的优点:
1、 通过组合而非继承的方式,实现了动态扩展对象的功能的能力。
2、 有效避免了使用继承的方式扩展对象功能而带来的灵活性差,子类无限制扩张的问题。
3、 充分利用了继承和组合的长处和短处,在灵活性和扩展性之间找到完美的平衡点。
4、 装饰者和被装饰者之间虽然都是同一类型,但是它们彼此是完全独立并可以各自独立任意改变的。
5、 遵守大部分GRASP原则和常用设计原则,高内聚、低偶合。
装饰者模式的缺点:
1、 装饰链不能过长,否则会影响效率。
2、 因为所有对象都是继承于Component,所以如果Component内部结构发生改变,则不可避免地影响所有子类(装饰者和被装饰者),也就是说,通过继承建立的关系总是脆弱地,如果基类改变,势必影响对象的内部,而通过组合(Decoator HAS A Component)建立的关系只会影响被装饰对象的外部特征。
3、只在必要的时候使用装饰者模式,否则会提高程序的复杂性,增加系统维护难度。