惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Martin Fowler
Martin Fowler
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
A
About on SuperTechFans
Apple Machine Learning Research
Apple Machine Learning Research
The Register - Security
The Register - Security
Vercel News
Vercel News
H
Hackread – Cybersecurity News, Data Breaches, AI and More
人人都是产品经理
人人都是产品经理
MyScale Blog
MyScale Blog
云风的 BLOG
云风的 BLOG
博客园_首页
U
Unit 42
T
Tailwind CSS Blog
G
GRAHAM CLULEY
F
Full Disclosure
V
Vulnerabilities – Threatpost
T
Tenable Blog
月光博客
月光博客
P
Privacy & Cybersecurity Law Blog
P
Privacy International News Feed
K
Kaspersky official blog
Scott Helme
Scott Helme
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
N
News and Events Feed by Topic
T
The Exploit Database - CXSecurity.com
N
News and Events Feed by Topic
有赞技术团队
有赞技术团队
Recent Commits to openclaw:main
Recent Commits to openclaw:main
L
LINUX DO - 最新话题
Recorded Future
Recorded Future
Application and Cybersecurity Blog
Application and Cybersecurity Blog
Help Net Security
Help Net Security
The GitHub Blog
The GitHub Blog
Cisco Talos Blog
Cisco Talos Blog
SecWiki News
SecWiki News
P
Proofpoint News Feed
Security Latest
Security Latest
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
罗磊的独立博客
S
Security Affairs
M
MIT News - Artificial intelligence
L
LINUX DO - 热门话题
美团技术团队
Simon Willison's Weblog
Simon Willison's Weblog
T
Threat Research - Cisco Blogs
Stack Overflow Blog
Stack Overflow Blog
Forbes - Security
Forbes - Security
Hugging Face - Blog
Hugging Face - Blog
博客园 - Franky
V
Visual Studio Blog

博客园 - ScottDa虾米

转:程序员从初级到中级10个秘诀 呵呵,很久没写随笔了,这次带来个新的小问题,在使用win7RTM过程中遇到的 ASP.NET中常用的26个优化性能方法 ERP和SAP的一些名词解释 乱七八糟网址大集合 中小企业成败关键在于老板 五种提高 SQL 性能的方法 Linq本质论 Windows Communication Foundation 详解 Windows Presentation Foundation 详解 .Net中的设计模式——Strategy模式 理解C# 3.0新特性之Extension方法浅议 LINQ 的演变及其对 C# 设计的影响 PetShop的系统架构设计 C# 3.0语言新特性(语言规范) .NET企业级应用架构设计的技术选型 “扫雷”游戏幕后 比尔盖茨:十条“金口玉言”-- 世界不会在意你的自尊 [转]三五个人十来条枪 如何走出软件作坊成为开发正规军
.Net中的设计模式——Decorator模式
ScottDa虾米 · 2008-03-31 · via 博客园 - ScottDa虾米

一、模式概述

一个场景是我们要为一个对象动态添加新的职责,这个职责并不修改原有的行为,而是在原有行为基础上添加新的功能,就好比装饰工人为一座新居的墙上涂抹上色彩缤纷的颜料一般。

 从我们拥有的面向对象的知识出发,为一个对象增加新的职责,完全可以利用继承机制,然而再通过实例化派生的子类,来获得新增的职责。由于需要在原有行为基础上添加新功能,此时父类的方法应该为虚方法,例如用户登录行为:

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;
}
}

实现的类图如下:

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


 这样的结果会导致类的数量会随着需求的变化而无限量的增加,同时有关权限验证的职责也没有能够得到充分的重用,这显然违背了面向对象设计的精神。

 还有一个添加职责的办法,就是利用组合,将原有的对象嵌入到一个新的类中,由此来完成对新职责的添加,例如:

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;
}
}

实现的类图如下:


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


 此时我们只需要将Employee对象赋给SecurityUser中的User属性就可以实现对Employee权限验证的登录。因此,我们避免了此前采用继承机制所面临的两个问题:

 1、 类的无限量增加(所谓的“类爆炸”);
 2、 权限验证职责的不可重用。

 然而组合却失去了继承的许多优势,其中,不具备对象的多态特质,就是最大的一个局限。例如,如下创建对象的方式就是错误的:

 User user = new SecurityUser();

 当一个方法体现为对User的操作时,此时SecurityUser就无法对User类型的对象进行替代,这就限制了软件的可扩展性。例如,在表示层逻辑中,会有一个登录页面将调用SignIn方法:

public class LoginPage
{
public static void SignIn(User user);
}

在这种情况下,SecurityUser对象就无法做为参数传入,那么,我们在前面所作的职责添加的努力岂不就是付诸东流了吗?

 如果仔细观察继承与组合的优劣,我们发现如果将继承机制与组合方式两者结合起来,将会起到意想不到的效果,此时,各自的优点弥补了各自的缺点,完美地解决了“为对象动态添加新的职责”的需求。

 新的解决方案如下图所示:


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


 此时,我将原来的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()方法执行的时序图如下:


 从时序图中可以看到,当我们调用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中,以上的实现可以用类图来表示:


 在类图中,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添加了加密的职能。

Source:Bruce Zhang's Blog