VB.net设计模式之单件模式(singleton)


开始第一种模式:单件模式(singleton)

 

概述:

          单件模式(singleton)模式要求在全局中,类只有一个实例。整个网站中的任何一个客户(任意一个web请求)在访问这个类的时候,访问的都是类 的同一个实例。常见的现实中的比喻是美国总统,在全世界范围内,某一时刻,只有一个总统。即在网站或程序在从开始到结束运行的过程中,只有一个该类的实例。

         如果做到让整个程序运行中只访问一个类的实例呢?首先当类实例不存在时,创建这个实例(建国时选举第一任总统)。然后,为了保证只使用一个类实例,那么必须不再新建类的实例(不再选举总统)。如何避免再次新建类的实例呢?可以有两种方式:

        第一:让全国人民保证,自己再也不选举总统(在整个程序中,不再去实例化这个类),这个太难,人数众多(程序太大)。

        第二:当需要总统的时候(比如:参加xxx国际会议?),不允许选举一个总统,只能到总统府去找。如果没有找到总统,则由议会推荐产生。

        第一种,在程序中就是,大家约定好,要用到某个类的实例的时候不要去new ,把职责归属于使用(类)者。第二种,就是在程序中开发出一个类,这个类不能new ,只能够通过一种方法获得,比如静态属性,静态方法等。职责归属于,类的开发者。在单件模式中,我们要讨论的就是第二种情况:如何设计一个只能获得一个实例的类。

动机:

        如何绕过常规的类构造器,提供一种机制,保证一个类只有一个实例。

意图:

         保证一个类在整个程序,网站运行中只存在一个类的实例。并提供一个全局访问点。

 VB.Net实现:

        实现一:简单实现 --不推荐使用

  1. '访问计数器--单件模式--简单实现
  2. Public NotInheritable Class CountClient0
  3.     Public IntCount As Int32 '计算值
  4.     Private Shared _CountClient As CountClient0 = Nothing '初始对象
  5.     Private Sub New()
  6.     End Sub
  7.     Public Shared ReadOnly Property CountClient() As CountClient0
  8.         Get
  9.             If _CountClient Is Nothing Then
  10.                 _CountClient = New CountClient0
  11.             End If
  12.             Return _CountClient
  13.         End Get
  14.     End Property
  15. End Class
评述:为什么称简单的实现呢,通过如上代码已基本实现全局只有一个类的实例,但在多线程情况下会有问题,为什么呢?问题就在于
  1.             If _CountClient Is Nothing Then
  2.                 _CountClient = New CountClient0
  3.             End If

这里的第一行,有可能有很多的程序同时访问这行(转换成汇编后,可能是很多行代码,因此并发访问这行可能性极大,等下将验证此问题),此时_CountClient均返回Nothing 。就是所谓的线性安全,在多线程环境下,会有问题。现在我们来检测是否存在线程安全问题,我们知道,线程安全主要是由于,多行同时访问上述代码第一行,都返回的是Nothing.这说明我们生成_CountClient对象的时间越长,这种可能行就越大。为了凸显这个问题,我们将这个简单实现的实例类改造如下:

  1. '访问计数器--单件模式--简单实现
  2. Public NotInheritable Class CountClient0
  3.     Public IntCount As Int32 '计算值
  4.     Private Shared _CountClient As CountClient0 = Nothing '初始对象
  5.     Private Sub New()
  6. '延长生成对象时间
  7.         For i = 0 To 10
  8.             Threading.Thread.Sleep(2000)
  9.             Dim b = i
  10.             For j = 1 To 100000
  11.                 b = j + i
  12.                 If b > 1000000 Then b = -100000
  13.             Next
  14.         Next
  15.     End Sub
  16.     Public Shared ReadOnly Property CountClient() As CountClient0
  17.         Get
  18.             If _CountClient Is Nothing Then
  19.                 _CountClient = New CountClient0
  20.             End If
  21.             Return _CountClient
  22.         End Get
  23.     End Property
  24. End Class

 测试代码:

Default.aspx

  1. <%@ Page Language="VB" AutoEventWireup="false" CodeFile="Default.aspx.vb" ValidateRequest ="false"  Inherits="_Default" %>

  2. <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

  3. <html xmlns="http://www.w3.org/1999/xhtml">
  4. <head runat="server">
  5.     <title>无标题页</title>
  6. </head>
  7. <body>
  8.     <form id="form1" runat="server">
  9.     <div>

  10.     <input id="Button1" onclick ="javascript:out();" type="button" value="button" />

  11.         <script>
  12.     function out(){
  13.     for(ii=0;ii<20;ii++){
  14.  window.open('/default2.aspx?I='+ii);
  15.  window.open('/Default.aspx?I='+ii);
  16.     
  17.     }
  18.     }
  19.         </script>
  20.     </div>
  21.     
  22.     </form>
  23. </body>
  24. </html>
Default.aspx.vb

  1. Partial Class _Default
  2.     Inherits System.Web.UI.Page



  3.     Protected Sub Page_Load(ByVal sender As ObjectByVal e As System.EventArgs) Handles Me.Load
  4.         Dim b = CountClient0.CountClient
  5.         b.IntCount += 1
  6.         Response.Write(b.IntCount & " I=" & Request("I") & "<Br>")
  7.     
  8.     End Sub
  9.     
  10. End Class

测试一下就会发现,前面弹出的页面值都等于1.说明他们返回了不同的对象。

为了实现多线程下的安全。进行如下设计

实现二:线程安全实现

  1. '访问计数器--单件模式--简单实现
  2. Public NotInheritable Class CountClient0
  3.     Public IntCount As Int32 '计算值
  4.     Private Shared _CountClient As CountClient0 = Nothing '初始对象
  5.     Private Shared b As Object = New Object
  6.     Private Sub New()
  7.         For i = 0 To 10
  8.             Threading.Thread.Sleep(2000)
  9.             Dim b = i
  10.             For j = 1 To 100000
  11.                 b = j + i
  12.                 If b > 1000000 Then b = -100000
  13.             Next
  14.         Next

  15.     End Sub
  16.     Public Shared ReadOnly Property CountClient() As CountClient0
  17.         Get
  18.             SyncLock (b)
  19.                 If _CountClient Is Nothing Then
  20.                     _CountClient = New CountClient0
  21.                 End If
  22.             End SyncLock

  23.             Return _CountClient
  24.         End Get
  25.     End Property
  26. End Class
评述:以上代码通过对辅助对象加锁方式实现了线程安全,因为加锁的代码只能有一个线程去访问。但是,这种方式增加了额外的开销,损失了性能(因为每次访问都加锁)。

因此为了解决上述缺陷,改进类设计如下:

实现三:双重加锁--不推荐使用

  1. '访问计数器--单件模式--简单实现
  2. Public NotInheritable Class CountClient0
  3.     Public IntCount As Int32 '计算值
  4.     Private Shared _CountClient As CountClient0 = Nothing '初始对象
  5.     Private Shared b As Object = New Object
  6.     Private Sub New()
  7.         For i = 0 To 10
  8.             Threading.Thread.Sleep(2000)
  9.             Dim b = i
  10.             For j = 1 To 100000
  11.                 b = j + i
  12.                 If b > 1000000 Then b = -100000
  13.             Next
  14.         Next
  15.     End Sub
  16.     Public Shared ReadOnly Property CountClient() As CountClient0
  17.         Get
  18.             If _CountClient Is Nothing Then
  19.                 SyncLock (b)
  20.                     If _CountClient Is Nothing Then
  21.                         _CountClient = New CountClient0
  22.                     End If
  23.                 End SyncLock
  24.             End If
  25.             Return _CountClient
  26.         End Get
  27.     End Property
  28. End Class

 评述:这就实现了线程安全,同时不是每次都需要锁辅助对象。只有第一次创建对象时加锁。但在.Net平台有更为高效和简洁的实现方式,如下:

实现四:静态初始化

  1. '访问统计器--单件模式--
  2. Public NotInheritable Class CountClient
  3.     Public IntCount As Int32 '计算值
  4.     Shared ReadOnly _CountClient = New CountClient()
  5.     Private Sub New()
  6.     End Sub
  7.     Public Shared ReadOnly Property Instance() As CountClient
  8.         Get
  9.             Return _CountClient
  10.         End Get
  11.     End Property
  12. End Class
评述:该方法通过依赖公共语言运行库来初始化变量。该方法是.net中实现单件模式的首选方法。当此类的任一个变量,属性被访问时就建立对象。

实现五:延迟初始化

  1. '访问计数器--单件模式--延迟初始化
  2. Public NotInheritable Class CountClient1
  3.     Public IntCount As Int32 '计算值
  4.     Private Sub New()
  5.     End Sub
  6.     Public ReadOnly Property Intance()
  7.         Get
  8.             '在这里进行延迟处理
  9.             Return Nested._CountClient
  10.         End Get
  11.     End Property
  12.     Class Nested
  13.         Private Sub New()
  14.         End Sub
  15.         Friend Shared _CountClient As CountClient1 = New CountClient1
  16.     End Class
  17. End Class
评述:把初始化工作交给了Nested类实现。实现了延迟处理。延时处理可以在需要或者必要的时候初始化这个类,解除了实现四的无法控制类初始化问题。

实现五:泛型单件
  1. '访问计数器--单件模式--泛型单件
  2. Public NotInheritable Class CountClient3(Of T)
  3.     Public IntCount As Int32 '计算值
  4.     Private Sub New()
  5.     End Sub
  6.     Public ReadOnly Property Intance() As CountClient3(Of T)
  7.         Get
  8.             '在这里进行延迟处理
  9.             Return Nested._CountClient
  10.         End Get
  11.     End Property
  12.     Class Nested
  13.         Private Sub New()
  14.         End Sub
  15.         Friend Shared _CountClient = New CountClient3(Of T)
  16.     End Class
  17. End Class
评述:实现四下的泛型解决方案。

注意事项:

1:使用Singleton模式有一个必要条件:在一个系统要求一个类只有一个实例时才应当使用单例模式。反过来,如果一个类可以有几个实例共存,就不要使用单例模式。

2:不要使用单例模式存取全局变量。这违背了单例模式的用意,最好放到对应类的静态成员中。

3:不要对单件类进行系列化。他违背了单件模式的初衷。



应用场景:
1:每台计算机可以有若干个打印机,但只能有一个Printer Spooler,避免两个打印作业同时输出到打印机。
2:一个具有自动编号主键的表可以有多个用户同时使用,但数据库中只能有一个地方分配下一个主键编号。否则会出现主键重复。
3:负载均衡对象。在负载均衡模型中,有多台服务器可提供服务,任务分配器随机挑选一台服务器提供服务,以确保任务均衡(实际情况比这个复杂的多)。这里,任务分配实例只能有一个,负责挑选服务器并分配任务。
4:Mdi子窗体,只能打开一个的情况下。

常见问题:
1:把数据库的链接设计成单件模式好不好,为什么?
--不要将数据库连接做成单例,因为一个系统可能会与数据库有多个连接,并且在有连接池的情况下,应当尽可能及时释放连接。在并发时 会损失性能 很大。Singleton模式由于使用静态成员存储类实例,所以可能会造成资源无法及时释放,带来问题。
2:静态类和单件的区别?
--单件具备面向对象的一些特点,比如继承等,静态类没有。
--静态类,Java中有说法为工具类,谈不上面向对象的概念
--单件似乎存在难回收问题(GC不自动回收)
应用实例:
1:日志记录


 

参考资料:

1:.Net设计模式(2)单件模式

2:.Net设计模式(7)单件模式


 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值