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

转载 2007年09月25日 09:22:00
讲解.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添加了加密的职能。

 

设计模式——装饰模式(Decorator)

要想正确理解设计模式,首先必须明确它是为了解决什么问题而提出来的。 设计模式学习笔记 ——Shulin 转载请注明出处:http://blog.csdn.net/zhshulin 1、概念 ...
  • u012909091
  • u012909091
  • 2014年08月18日 20:23
  • 18599

浅谈JAVA设计模式之——装饰模式(Decorator)

一、概述 动态地给一个对象添加一些额外的职责。就增加功能来说,Decorator模式相比生成子类更为灵活。 二、适用性 1.在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责。 2.处...
  • l1028386804
  • l1028386804
  • 2015年05月03日 23:56
  • 2053

【设计模式】之装饰器(Decorator)模式

【设计模式】之装饰器(Decorator)模式 通常给对象添加功能,要么直接修改对象添加相应的功能,要么派生对应的子类来扩展,抑或是使用对象组合的方式。显然,直接修改对应的类这种方式并不可取。在面...
  • Xiao_Spring
  • Xiao_Spring
  • 2017年03月29日 15:32
  • 614

设计模式之八 --- 装饰模式(Decorator)

【1】基本概念           装饰模式(Decorator),动态地给一个对象添加一些额外的职责,就增加功能来说,装饰模式比生成子类更为灵活。 【2】简单分析           我们先来...
  • cjjky
  • cjjky
  • 2012年04月19日 20:12
  • 14361

设计模式:Visitor模式

Visitor模式是一个用起来很简单,理解起来可能稍微有一点困难的模式。不过明白了之后就清楚了,其实也是非常的简单。问题需要向对象结构中增加新的方法,但是增加起来会很费劲或者会破坏设计。 案例举一个例...
  • superbeck
  • superbeck
  • 2010年02月25日 14:02
  • 10970

[设计模式]-装饰器模式(Decorator)

定义: 动态地给一个对象添加一些额外的职责。就增加功能来说,装饰模式比生成子类更为灵活。 对象结构型模式...
  • qust_2011
  • qust_2011
  • 2016年11月30日 19:15
  • 480

.NET开发中最常用的设计模式

整个设计模式贯穿一个原理:面对接口编程,而不是面对实现.目标原则是:降低耦合,增强灵活性.   一些基本的设计模式 Abstract Factory:提供一个创建一系列相关或相互依赖对象的接口...
  • CsethCRM
  • CsethCRM
  • 2014年05月26日 09:06
  • 1768

设计模式总结之Composite Pattern(组合模式)

组合模式,将对象组合成树形结构以表示“部分-整体”的层次结构,组合模式使得用户对单个对象和组合对象的使用具有一致性。...
  • cooldragon
  • cooldragon
  • 2016年08月11日 00:49
  • 1148

设计模式 - 装饰者模式(Decorator Pattern) Java的IO类 使用方法

装饰者模式(Decorator Pattern) Java的IO类 使用方法 本文地址: http://blog.csdn.net/caroline_wendy/article/details/2...
  • u012515223
  • u012515223
  • 2014年05月23日 16:18
  • 2668

设计模式:Bridge模式

如果你想要写一个游戏,并且想让这个游戏同时支持PC和手机,那么怎么样的设计可以避免写两套代码,并且不影响可扩展性呢?说起来还是比较简单的,只要把对平台的依赖部分抽取成抽象的接口(比如说绘图部分),并且...
  • superbeck
  • superbeck
  • 2010年10月27日 18:06
  • 5001
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:.Net中的设计模式——Decorator模式
举报原因:
原因补充:

(最多只允许输入30个字)