zoukankan      html  css  js  c++  java
  • .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添加了加密的职能。

  • 相关阅读:
    java Class.getResource和ClassLoader.getResource
    Ext Grid控件的配置与方法
    BLANK_IMAGE_URL
    js中变量和jsp中java代码中变量互相访问解决方案
    PL/SQL
    滴滴2021后端开发岗笔试:
    顺丰科技2021研发岗笔试:贪心算法应用
    2021顺丰科技研发笔试: 深度优先算法的应用
    动态规划算法轻松解决01背包,完全背包,多重背包问题
    寻找二叉树的最近公共祖先
  • 原文地址:https://www.cnblogs.com/wayfarer/p/498125.html
Copyright © 2011-2022 走看看