licongjie的博客

专心、专注、专业
随笔 - 26, 文章 - 1, 评论 - 207, 引用 - 1
数据加载中……

设计模式杂谈:创建型模式之单件模式(Singleton)

        前几讲链接:
      1、设计模式杂谈:开头篇
      2、设计模式杂谈:创建型模式之工厂方法(Factory Method)

      在上一讲“设计模式杂谈:创建型模式之工厂方法(Factory Method)”中,已经通过一个案例对工厂方法进行了讲述。也有不少朋友看了提出了一些建议,确实,在上一讲中,只是简单的讲了工厂方法的用法,但有些地方并不适合于实际应用。主要是最后的创建工厂方法实例时,我把这个工厂方法的创建与该工厂方法创建的具体对象,以及它的执行都放在一起,显然这种做法是错误的,如果这样做的话,还不如直接创建要执行的具体对象,没有必要再搞一个工厂方法进去,有点多此一举的味道,就好象一个包装盒,外面又包了一层,但却并没有起到一定的作用。在这一讲中,在讲单件模式之前,我先对上一讲中的问题进行一些必要的忧化,可能这样会更符合实际的应用。

      现在我们再回头看一下上一讲中最后实现的一段代码:

 1double basicSalary;
 2                double result;
 3
 4                basicSalary = Convert.ToDouble(txtBasicSalary.Text.Trim());
 5
 6                IBLL.IFactory factory = new Factory.ChineseFactory();
 7                IBLL.ISalary salary = factory.CreateSalary();
 8
 9                result = salary.Calculate(basicSalary);
10
11                lbSalary.Text = result.ToString();

      我们现在可以把这个系统想的复杂一些,如果说工资的计算在很多模块里都要用到,按现在的这种做法,则必须在要用到的模块里,重新通过IBLL.IFactory factory = new Factory.ChineseFactory();这个来创建工厂方法,再通过这个工厂来创建相应的ISalary对象,那么一旦需求改了,需要换成美国公司的,那么必须修改每一个模块,这样做的话,那么这个工厂方法真是一个鸡肋了,还不如不要了,可能更方便。所以首选必须把具体工厂的创建放在一个单独的地方,这样就可以统一管理,一旦需求改了,只要改变该地方就可以了。
      好,下面我就就再创建了个FactoryUtility.cs文件,具体代码:
 1using System;
 2using System.Collections.Generic;
 3using System.Text;
 4using System.Configuration;
 5using System.Reflection;
 6
 7namespace DesignPattern.Common
 8{
 9    public class FactoryUtility
10    {
11        public IBLL.IFactory GetFactory()
12        {
13            IBLL.IFactory factory = new Factory.ChineseFactory();
14
15            return factory;
16        }

17
18    }

19}

20

      这样我们就可以把上面实现部分代码修改成如下所示:
 1double basicSalary;
 2                double result;
 3
 4                basicSalary = Convert.ToDouble(txtBasicSalary.Text.Trim());
 5
 6                IBLL.IFactory factory = new FactoryUtility().GetFactory();
 7                IBLL.ISalary salary = factory.CreateSalary();
 8
 9                result = salary.Calculate(basicSalary);
10
11                lbSalary.Text = result.ToString();
      
      这样的话,如果要满足美国公司的需求,则只要修改FactoryUtility.cs一个文件即可,不用变动其它。经过这一忧化,的确有所改观,但一旦需求变化的话,还是要修改代码。好我们再进一步来讨论这个问题,现在如果 要满足美国公司的要求,则需要修改代码,那怎么样才能不用修改代码呢,这个通过配置+反射就能够很好的解决。首先,建立应用程序配置文件App.Config,进行如下配置:

1<?xml version="1.0" encoding="utf-8" ?>
2<configuration>
3  <appSettings>
4    <add key="FactoryName" value="DesignPattern.Factory.ChineseFactory"/>
5  </appSettings>
6</configuration>

      通过这个配置,我们可以读取到当前工厂方法的类,再通过反射机制来创建该工厂对象即可,通过进一步的忧化,FactoryUtility.cs文件修改后代码如下:
 1using System;
 2using System.Collections.Generic;
 3using System.Text;
 4using System.Configuration;
 5using System.Reflection;
 6
 7namespace DesignPattern.Common
 8{
 9    public class FactoryUtility
10    {
11        public IBLL.IFactory GetFactory()
12        {
13            string factoryName = ConfigurationSettings.AppSettings["FactoryName"];
14            factory = (IBLL.IFactory)Assembly.Load("Salary").CreateInstance(factoryName);
15
16            return factory;
17        }

18
19    }

20}

21

      这时我们再来看看,如果要满足美国企业的时候,我们再不需要去改变程序代码,只要修改配置文件App.Config即可:

1<?xml version="1.0" encoding="utf-8" ?>
2<configuration>
3  <appSettings>
4    <add key="FactoryName" value="DesignPattern.Factory.AmericanFactory"/>
5  </appSettings>
6</configuration>

      当然,这里对于客户来说还不是很直观,这个可以再进一步的去忧化配置文件,这里就不再讲述了。

      到此为上,经过上述的一系列优化后,已经变得很灵活了,能基本适应美国公司和中国公司的需求。

      好,我们现在转到正题(呵,好象也不是什么正题了)。下面我们来看一下创建型设计模式的单件模式,其实这个模式是比较特殊的一种,也是最好理解的。顾名思义,就是只创建一个实例,这在一些特定的场合是非常有用的,比如说最常见的,就是我们进行数据库操作时必须要创建的一个sqlConnection对象,由于数据库连接都是通过这个对象来操作的,所以我们没有必要创建该对象的多个实例,只创建一个就可以了,这样即节省资源又可以避免一些异常的发生,更容易控制。

      其实,在程序开发中,我们已经在不知不觉的使用单件模式了。最简单的就是使用static关键字定义对象,其实这就是一种单件模式了。常见的单件模式的样式如下代码所示:

 1private static object ob;
 2        public static object OB
 3        {
 4            get
 5            {
 6                if (ob == null)
 7                {
 8
 9                    ob = new object();
10
11                }

12                return ob;
13            }

14        }

      也可以对该单件模式进行改进,如对于多线程应用程序,我们就应该在上面加一把锁,以防该对象被多次创建:
  private static readonly object pad = new object();
        private static object ob;
        public static object OB
        {
            get
            {
               if(ob == null)
               {
                  lock (pad)
                  {
                     if (ob == null)
                     {
                                        
                        ob = new object();
                      }
                }
               }
                return ob;
            }
        }

      如果有些朋友想更多的了解单件模式的话,请看TerryLee写的设计模式系列“ .NET设计模式系列 ”。

      现在我们再回头看看原来的程序,一旦部署后,那么其实这个工厂方法也就确定,由于我们真正要用到的实现方法对象是由该工厂方法来创建的,所以该工厂方法没有必要被多次实例化,在整个系统中我们只需要一个实例久可以了,由这个工厂实例我们就可以创建在计算工资时用到的对象,因此在这里我们就可以用到单件模式了。
   
      再一次修改FactoryUtility.cs文件,得到新的代码:
 1using System;
 2using System.Collections.Generic;
 3using System.Text;
 4using System.Configuration;
 5using System.Reflection;
 6
 7namespace DesignPattern.Common
 8{
 9    public class FactoryUtility
10    {
11        private static readonly object ton = new object();
12        private static IBLL.IFactory factory = null;
13
14        public static IBLL.IFactory Factory
15        {
16            get
17            {
18                if (factory == null)
19                {
20                    lock (ton)
21                    {
22                        if (factory == null)
23                        {
24
25                            string factoryName = ConfigurationSettings.AppSettings["FactoryName"];
26                            factory = (IBLL.IFactory)Assembly.Load("Salary").CreateInstance(factoryName);
27
28                        }

29                    }

30                }

31                return factory;
32            }

33        }

34    }

35}

36

      最后,我们再来看一下,最后实现代码:
 1 double basicSalary;
 2                double result;
 3
 4                basicSalary = Convert.ToDouble(txtBasicSalary.Text.Trim());
 5
 6
 7                IBLL.ISalary salary = FactoryUtility.Factory.CreateSalary();
 8
 9                result = salary.Calculate(basicSalary);
10
11                lbSalary.Text = result.ToString();

      好了,这篇就讲到这里了,希望各位朋友能看得懂。

       源码下载(单件模式) 

      下一篇:设计模式杂谈:创建型模式之抽象工厂模式(Abstract Factory)

posted on 2006-12-11 14:18 李.net 阅读(1772) 评论(9)  编辑 收藏 所属分类: Design & Pattern

评论

#1楼    回复  引用  查看    

ODRM模式
http://www.cnblogs.com/Files/mail-ricklee/ODRM.rar
2006-12-11 17:57 | aigoman [未注册用户]

#2楼    回复  引用  查看    

private static readonly object pad = new object();
private static object ob;
public static object OB
{
get
{
if (ob == null)
{
lock (pad)
{
ob = new object();
}
}
return ob;
}
}

改成下面这样应该否则 lock 没意义

private static readonly object pad = new object();
private static object ob;
public static object OB
{
get
{

lock (pad)
{
if (ob == null)
{
ob = new object();
}
}

return ob;
}
}
2006-12-11 20:32 | 曲滨      

#3楼 [楼主]   回复  引用  查看    

@曲滨
确实,非常感谢,现已改正
2006-12-11 21:41 | 李.net      

#4楼    回复  引用  查看    

感觉还好,比上一个更好些!!
2006-12-11 22:39 | yunhuasheng      

#5楼    回复  引用  查看    

应该是这样的。 double lock 的惯用法。
原文代码里面的不论 ob 是否为 null, 进去都要 lock 一下,不好。

private static readonly object pad = new object();
private static object ob;
public static object OB
{
get
{
if(ob==null){ // 注意这一行
lock (pad)
{
if (ob == null)
{

ob = new object();
}
}
}
return ob;
}
}
2006-12-12 01:18 | 木野狐      

#6楼    回复  引用  查看    

上面我说的不准确。应该叫做 double-checked locking.
下面这段代码里也多了无谓的 lock :

public static IBLL.IFactory Factory
15 {
16 get
17 {
18 if (factory == null)
19 {
20 lock (ton)
21 {
22 if (factory == null)
23 {
24 lock (ton) // 这里多余
25 {
26 string factoryName = ConfigurationSettings.AppSettings["FactoryName"];
27 factory = (IBLL.IFactory)Assembly.Load("Salary").CreateInstance(factoryName);
28 }
29 }
30 }
31 }
32 return factory;
33 }
34 }

关键点是:lock 只要一次即可,在lock 的内外各做一次是否为 null 的检验。
2006-12-12 01:23 | 木野狐      

#7楼    回复  引用  查看    

参考这里:
http://www.javaworld.com/jw-02-2001/jw-0209-double.html
2006-12-12 01:24 | 木野狐      

#8楼 [楼主]   回复  引用  查看    

@木野狐
受教了,呵呵,这些细节问题都没有去好好研究一下。现在把源代码和文章的一些地方都 作了修改。
2006-12-12 09:43 | 李.net      

#9楼    回复  引用  查看    

@李.net
客气:)
大家一起分享,交流。
2006-12-12 11:02 | 木野狐      

标题  
姓名  
主页
Email (只有博主才能看到) 
验证码 *  看不清,换一张
内容(请不要发表任何与政治相关的内容)  
  登录  使用高级评论  新用户注册  返回页首  恢复上次提交      
该文被作者在 2006-12-15 12:23 编辑过
 
向地震灾区捐赠爱心