用实例解说Dot Net设计模式——装饰模式
1.装饰模式的概述
2.1 什么是装饰模式
在不改变对象的前提下,动态增加其功能,即我们不希望改变原有的类,或采用创建子类的方法增加功能,这种情况下需要采用装饰模式。
2.2结构
修饰一个对象后,其接口不应该发生变化;否则这个对象不能被原有调用者使用,修饰失去了意义。由此引出了装饰模式结构最重要的一点,即装饰者和被装饰者具有相同的接口。换句话说,动态增加的功能不应该破坏已有的接口。
装饰模式的结构如下图所示。
装饰模式的结构图
(1)Component:定义一个对象接口,可以动态添加这些对象的功能。
(2)ConcreteComponent:定义一个对象,可以为其添加一些功能。
(3)Decorator:维持一个对Component对象的引用,并定义与Component接口一致的接口。
(4)ConcreteDecorator:为组件添加功能。
2.3 什么情况下是用装饰模式
以下情况使用装饰模式。
(1)在不影响其他对象的情况下,以动态且透明的方式添加单个对象的功能。
(2)处理那些可以撤销的功能。
(3)不能采用生成子类的方法扩充时。
2.4实现时的注意事项
实现时的注意事项如下。
(1)接口的一致性:装饰模式中被装饰对象对用户而言是透明的,用户仍然可以直接访问被装饰对象,并且装饰后的对象在访问时不就变化。如果接口不一致,尽管也达到了为对象增加功能的目的,但并不是装饰模式。
(2)保持被装饰类的简单性:被装饰类需要功能单一,这样才能使结构更灵活。比如显示最新新闻的组件,由标题和主体组成。
其中的核心组件应该显示若干条最近的新闻,我们可以用一个组件显示新闻,加一个显示标题和样式。解决问题的方法是核心组件保持最小的功能,显示新闻标题,每个装饰器也只完成一项功能。我们可以增加一个显示more按钮的装饰器,使得装饰器可以通过组合完成更多的功能。
2.5效果
使用装饰模式的优点是比静态继承灵活,并可避免在层次结构高层的类有太多的特征;缺点是产生许多小对象。
2.用一个实例来解说装饰模式
做BS的朋友们都会遇到网页组件装饰器的实现问题,我们希望为用户自定义组件创建可以通用的装饰器。为此需要定义一个装饰器的基类,代码如下:
2 Inherits System.Web.UI.UserControl
3 Private _Control As String
4 Private _ContentPane As Web.UI.Control
5 Public Property Control() As String
6 Get
7 Return _Control
8 End Get
9 Set(ByVal Value As String)
10 _Control = Value
11 End Set
12 End Property
13 Public Property ContentPane() As Web.UI.Control
14 Get
15 Return _ContentPane
16 End Get
17 Set(ByVal Value As Web.UI.Control)
18 _ContentPane = Value
19 End Set
20 End Property
21
22 Public Sub AddControl(ByVal c As UserControl)
23 _ContentPane.Controls.Add(c)
24 End Sub
25End Class
26
在具体的装饰者中,后台的可执行代码很少,主要定义装饰界面,在SubDecorator中定义了带有图形边框的界面:
2 Inherits Decorator
3 <System.Diagnostics.DebuggerStepThrough()> Private Sub InitializeComponent()
4
5 End Sub
6 Protected WithEvents cp As System.Web.UI.HtmlControls.HtmlTableCell
7 Private designerPlaceholderDeclaration As System.Object
8 Private Sub Page_Init(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Init
9 InitializeComponent()
10 Me.ContentPane = Me.cp
11 End Sub
12 Public Sub Page_load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
13 If Me.Control <> "" Then
14 Dim c As UserControl = LoadControl(Me.Control)
15 Me.AddControl(c)
16 End If
17 End Sub
18End Class
在另一个装饰器中如下定义了可以使被装饰者滚动显示的SubDecoratorB.
Inherits Decorator
Protected WithEvents Top As System.Web.UI.WebControls.Literal
Protected WithEvents Buttom As System.Web.UI.WebControls.Literal
Protected WithEvents cp As System.Web.UI.WebControls.Panel
#Region " Web 窗体设计器生成的代码 "
'该调用是 Web 窗体设计器所必需的。
<System.Diagnostics.DebuggerStepThrough()> Private Sub InitializeComponent()
End Sub
'注意: 以下占位符声明是 Web 窗体设计器所必需的。
'不要删除或移动它。
Private designerPlaceholderDeclaration As System.Object
Private Sub Page_Init(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Init
'CODEGEN: 此方法调用是 Web 窗体设计器所必需的
'不要使用代码编辑器修改它。
InitializeComponent()
Me.ContentPane = Me.cp
End Sub
#End Region
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
'在此处放置初始化页的用户代码
If Me.Control <> "" Then
Dim c As UserControl = LoadControl(Me.Control)
Me.AddControl(c)
Me.Top.Text = ShowRollBegin("100")
Me.Buttom.Text = ShowRollEnd()
End If
End Sub
Private Function ShowRollBegin(ByVal strDisplayHeight) As String
Return "<marquee style='bottom:0px;height:'" & strDisplayHeight & "px;top:0px' id='News' scrollamount=1 scrolldelay=10 behavior='scroll' direction='up' onmouseover='this.stop()' onmouseout='this.start()'>"
End Function
Private Function ShowRollEnd() As String
Return "</marquee>"
End Function
End Class
我们定义一个很简单的HelloWorld用户组件用来测试,这个组件没有后台代码,仅仅有一个Lable组件:
<asp:Label Runat="server" id="Label2">HelloWorld</asp:Label>
我们用上面的两个装饰器装饰这个组件:
'在此处放置初始化页的用户代码
Dim c As Decorator = LoadControl("SubDecorator.ascx")
Dim d As Decorator = LoadControl("SubDecoratorB.ascx")
d.Control = "HelloWorld.ascx"
Me.Controls.Add(c)
c.AddControl(d)
End Sub
感兴趣的朋友可以做一个例子看看效果,别看上面一大堆代码,其实仔细观察不难看出实际上是几个UserControl的相互套用来实现装饰模式的。
3.我们在看一个.NET中的装饰模式实例
BufferedStream是经典的装饰模式,GOF在《设计模式》中已有介绍。并且在多种语言平台上都有相同的实现,在.NET中也不例外。
BufferedStream的作用是为另一流上的读写操作添加一个缓冲层,缓冲区是内存中的字节块,用于缓存数据。从而减少对操作系统的调用次数,缓冲区可提高读取和写入性能。
Stream是流的基类,抽象了对流的操作。根据流的来源和性质的不同,Stream的子类有System.Data.OracleClient.OracleBFile,System.Data.OracleClient.OracleLob,System.
IO.BufferedStream,System.IO.FileStream,System.IO.MemoryStream,System.Net.Sockets.NetworkStream和System.Security.Cryptography.CryptoStream。
如FileStream可以打开一个文件并以流的方式读写文件,MemoryStream读写内存中的数据流,而NetworkStream则操作Scokets连接的TCP流。其中,BufferedStream非常特殊,它不直接创建英雄模范种类型的流,而是在已有流上执行附加的操作。由于它继承了Stream,因此使用上与一般的流没有不同,只是为现有的流增加了缓冲功能。
通过采用装饰模式,BufferedStream使Stream具有了缓存功能而不用改动接口。这就是装饰模式所要达到的效果。这里就不再举具体的例子了,其实很简单,朋友可以自己试着写一下.