.Net中的设计模式——Decorator模式

讲解.Net Framework中的Decorator模式。

请访问:.Net中的设计模式——Decorator模式

一、模式概述

一个场景是我们要为一个对象动态添加新的职责,这个职责并不修改原有的行为,而是在原有行为基础上添加新的功能,就好比装饰工人为一座新居的墙上涂抹上色彩缤纷的颜料一般。
从我们拥有的面向对象的知识出发,为一个对象增加新的职责,完全可以利用继承机制,然而再通过实例化派生的子类,来获得新增的职责。由于需要在原有行为基础上添加新功能,此时父类的方法应该为虚方法,例如用户登录行为:
public class User
{
 public virtual void SignIn()
 {
  Console.WriteLine("The User Sign In.");
 }
}
如果需要为用户登录行为增加权限验证的职责,可以定义一个子类继承User类:
public class SecurityUser:User
{
 public override void SignIn()
 {
  if (IsValid())
  {
   base.SignIn();
  }
  else
  {
   throw new NotAuthorizationException();
  }
 }
 private bool IsValid()
 {
  //略;
  return true;
 }
}
实现的类图如下: 

dec01.gif

然而继承机制的一个局限是它只能静态地添加对象职责,一旦添加的职责有变,例如客户需求为登录行为添加日志记录功能,虽然我们可以再定义一个子类,重写SignIn()方法,然而我们却不能控制职责添加的时机与方式。此外,当User具有本身的继承体系时,则其并不能重用SecurityUser的职责。例如,User同时具有另外一个子类Employee,则Employee类的登录行为仍然沿用了其父类User的登录行为,而不具备权限验证的职责。如果需求要求Employee同样要验证登录权限,就必须再为它创建一个子类SecurityEmployee,如图: 

dec02.gif

这样的结果会导致类的数量会随着需求的变化而无限量的增加,同时有关权限验证的职责也没有能够得到充分的重用,这显然违背了面向对象设计的精神。
还有一个添加职责的办法,就是利用组合,将原有的对象嵌入到一个新的类中,由此来完成对新职责的添加,例如:
public class SecurityUser
{
 private User m_user;
 public User User
 {
  get {return m_user;}
  set {m_user = value;}
 }
 public void SignIn()
 {
  if (IsValid())
  {
   m_user.SignIn();
  }
else
  {
   throw new NotAuthorizationException();
  }

 }
 private bool IsValid()
 {
  //略;
  return true;
 }
}
实现的类图如下:

dec03.gif

比起继承机制而言,组合方式更加的灵活,尤其当面对User具有自身的继承体系时,如上的设计不需要任何修改,就能从容的应对: 

dec04.gif

此时我们只需要将Employee对象赋给SecurityUser中的User属性就可以实现对Employee权限验证的登录。因此,我们避免了此前采用继承机制所面临的两个问题:
1、 类的无限量增加(所谓的“类爆炸”);
2、 权限验证职责的不可重用。
然而组合却失去了继承的许多优势,其中,不具备对象的多态特质,就是最大的一个局限。例如,如下创建对象的方式就是错误的:
User user = new SecurityUser();
当一个方法体现为对User的操作时,此时SecurityUser就无法对User类型的对象进行替代,这就限制了软件的可扩展性。例如,在表示层逻辑中,会有一个登录页面将调用SignIn方法:
public class LoginPage
{
 public static void SignIn(User user);
}
在这种情况下,SecurityUser对象就无法做为参数传入,那么,我们在前面所作的职责添加的努力岂不就是付诸东流了吗?
如果仔细观察继承与组合的优劣,我们发现如果将继承机制与组合方式两者结合起来,将会起到意想不到的效果,此时,各自的优点弥补了各自的缺点,完美地解决了“为对象动态添加新的职责”的需求。
新的解决方案如下图所示: 

dec05.gif

实际上,如上的类图结构就是设计模式中的Decorator模式,User是被装饰的对象,而SecurityUser则是Decorator,也就是我们所谓的“装饰工”。在这里,SecurityUser是一个具体类,如果有新的装饰需求,例如之前提到的增加日志记录功能,同样需要建立装饰类。此时,对于具体的装饰类而言,具有一些相同的逻辑,在此前提下,可以为其增加一个Decorator抽象类: 

dec06.gif

此时,我将原来的SecurityUser类更名为SecuritySignInDecorator,便于理解。SignInDecorator是一个抽象类,继承了User类,同时User类对象又作为一个属性存在于SignInDecorator抽象类中。注意,这里的组合方式其实有多种实现方式。如作为一个属性,或者作为构造函数的参数等等。
抽象类SignInDecorator的代码如下所示:
public abstract SignInDecorator:User
{
 private User m_user;
 public User User
 {
  get {return m_user;}
  set {m_user = value;}
 }
 public override void SignIn()
 {
  if (m_user != null)
  {
   m_user.SignIn();
  }
 }
}
而SignInDecorator的子类定义则如下:
public class SecuritySignInDecorator:SignInDecorator
{
 public override void SignIn()
 {
  if (IsValid())
  {
   base.SignIn();
  }
  else
  {
   throw new NotAuthorizationException();
  }
 }
 private bool IsValid()
 {
  //略;
  return true;
 }
}
public class LoggingSignInDecorator:SignInDecorator
{
 public override void SignIn()
 {
  base.SignIn();
  Logging();  
 }
 private void Logging()
 {
  //略;
 }
}
目前的结构完全解决了前面利用继承或组合所出现的问题,避免了类的无限增加,权限验证或者日志记录的职责也能很好地重用,同时权限验证和日志记录等装饰类由于同样继承了User,因此根据多态原理,是可以完全替换User类型的对象的。
此外,利用Decorator模式还可以解决动态组合装饰的问题,例如为SignIn()方法既添加权限验证功能,又添加日志记录功能,此时并不需要新增一个类。实现如下:
User user = new User();
SignInDecorator securtiyDec = new SecuritySignInDecorator();
securityDec.User = user;
SignInDecorator loggingDec = new LoggingSignInDecorator();
loggingDec.User = securityDec;
loggingDec.SignIn();
loggingDec.SignIn()方法执行的时序图如下:

dec07.gif

从时序图中可以看到,当我们调用LoggingSignInDecorator对象的SignIn()方法时,因为LoggingSignInDecorator对象的User属性值为SecuritySignInDecorator对象,所以将执行SecuritySignInDecorator对象的SignIn()方法,该方法会先执行IsValid()私有方法,然而再调用User属性对象的SignIn()方法。由于SecuritySignInDecorator的User属性值为User对象,因此执行User对象的SignIn()方法。待SignIn()方法执行完毕,最后再执行LoggingSignInDecorator对象的Logging()方法。
注意上述的时序图,如果在LoggingSignInDecorator对象的SignIn()方法中,Logging()方法放在base.SignIn()前,则应该先执行Logging()方法,然后才是SignIn()方法。
不需要添加新的Decorator对象,通过上述的实现方式,我们就轻易地完成了对User对象SignIn()方法权限控制和日志记录的装饰。 

二、.Net Framework中的Decorator模式

在.Net Framework中,有关流的处理就使用了Decorator模式。我们知道,所有的流操作都有一个共同的基类System.IO.Stream,它是一个抽象类,主要包含了Read、Write等行为。而针对文件流和网络流定义的类FileStream和NetworkStream,都继承了Stream类,它们的读写操作的实现自然是不同的。然而,当我们需要提高流读写性能的时候,不管是文件流还是网络流,.Net都提供了同样的方式,即通过Buffer存放流数据以达到性能改进的目的。此时,Buffer的作用对于流的读写操作而言,就相当于一个装饰的作用。同样的,如果我们要求对文件流或网络流的数据读写进行加密操作,以保障数据的安全,那么这个加密的职责同样是一种装饰作用,并且Buffer与加密职责是并行不悖的,有时候也许需要为流操作共同加上这两项职能,这些要求完全符合Decorator模式。
在.Net Framework中,以上的实现可以用类图来表示: 

dec08.gif

在类图中,BufferedStream和CryptoStream就相当于Decorator类,不过在这里并没有定义抽象的Decorator类,因为它们不需要有共同的逻辑进行抽象。
.Net Framework对于Stream类的定义如下:
public abstract class Stream:MashalByRefObject,IDisposable
{
 static Stream()
 {
  Stream.Null = new Stream.NullStream();
 }
 protected Stream()
 {
  this._asyncActiveCount = 1;
 }
 public abstract int Read([In,Out]byte[] buffer, int offset, int count);
 public abstract void Write(byte[] buffer, int offset, int count);
 //……
 [NonSerialized]
 private int _asyncActiveCount;
}
注意在BufferedStream和CryptoStream类中是在构造函数中传入要装饰的对象:
public sealed class BufferedStream:Stream
{
 public BufferedStream(Stream stream):this(stream,0x1000){}
 public BufferedStream(Stream stream, int bufferSize)
{
      if (stream == null)
      {
            throw new ArgumentNullException("stream");
      }
      if (bufferSize <= 0)
      {
            throw new ArgumentOutOfRangeException("bufferSize", string.Format(CultureInfo.CurrentCulture, Environment.GetResourceString("ArgumentOutOfRange_MustBePositive"), new object[] { "bufferSize" }));
      }
      this._s = stream;
      this._bufferSize = bufferSize;
      if (!this._s.CanRead && !this._s.CanWrite)
      {
            __Error.StreamIsClosed();
      }
}
public override int Read([In, Out] byte[] array, int offset, int count)
{
      //……
      if (this._s == null)
      {
            __Error.StreamIsClosed();
      }
      int num1 = this._readLen - this._readPos;
      if (num1 == 0)
      {
            //处理buffer的操作
            num1 = this._s.Read(this._buffer, 0, this._bufferSize);
            if (num1 == 0)
            {
                  return 0;
            }
            this._readPos = 0;
            this._readLen = num1;
      }
      //……
      Buffer.InternalBlockCopy(this._buffer, this._readPos, array, offset, num1);
      this._readPos += num1;
      if (num1 < count)
      {
            int num2 = this._s.Read(array, offset + num1, count - num1);
            num1 += num2;
            this._readPos = 0;
            this._readLen = 0;
      }
      return num1;
}
//Write与其它方法略
private Stream _s;
}
BufferedStream类中private字段_s为Stream类型,在Read()方法中,处理了Buffer的操作后,就将调用_s的Read()方法,以此实现对Stream对象Read行为的装饰功能。
CryptoStream类的实现与BufferedStream类似,都是继承了抽象类Stream,同时通过构造函数的参数引入一个Stream对象。
在使用BufferedStream和CryptoStream时,可以将FileStream或NetworkStream等Stream子类对象通过其构造函数传入,以达到装饰的目的。由于其本身也继承了Stream类,因此它们互相之间也可以装饰,例如下面的代码:
public static void EncryptTextToFile(String Data, String FileName, byte[] Key, byte[] IV)
{
    try
    {
        FileStream fStream = File.Open(FileName, FileMode.OpenOrCreate); 

        // Create a new Rijndael object.
        Rijndael RijndaelAlg = Rijndael.Create();

        // Create a CryptoStream using the FileStream
        // and the passed key and initialization vector (IV).
        CryptoStream cStream = new CryptoStream(fStream,
        RijndaelAlg.CreateEncryptor(Key, IV),
        CryptoStreamMode.Write);
    // Create a StreamWriter using the CryptoStream.
        StreamWriter sWriter = new StreamWriter(cStream);
    try
    {
        // Write the data to the stream to encrypt it.
        sWriter.WriteLine(Data);
    }
    catch (Exception e)
    {
        Console.WriteLine("An error occurred: {0}", e.Message);
    }
    finally
    {       
        sWriter.Close();
        cStream.Close();
        fStream.Close();
    }
 }
 catch (CryptographicException e)
 {
     Console.WriteLine("A Cryptographic error occurred: {0}", e.Message);
 }
 catch (UnauthorizedAccessException e)
 {
     Console.WriteLine("A file error occurred: {0}", e.Message);
 }
}
在这里,就是将FileStream对象通过如下代码组合在CryptoStream对象中:
CryptoStream cStream = new CryptoStream(fStream,
        RijndaelAlg.CreateEncryptor(Key, IV),
        CryptoStreamMode.Write);
然后StreamWriter再利用该CryptoStream对象进行写操作时,写到文件FileName中的,就是加密后的数据。也就是说,我们动态地为FileStream添加了加密的职能。

posted @ 2006-09-07 23:04  张逸  阅读(6486)  评论(12编辑  收藏  举报