设计模式之单例模式
理论篇
单例模式:保证一个类仅有一个实例,并提供访问他的一个全局访问点。
Singleton类:
class Singleton { private static Singleton instance; private Singleton() //构造方法为private,防止外界利用new构造函数创建此类实例的可能 { } public static Singleton GetInstance() //此方法是获取实例的唯一全局访问点 { return instance ?? (instance = new Singleton()); //??空合并运算符,instance不为空就返回instance,为空就创建实例instance } }
客户端代码:
Singleton s1= Singleton.GetInstance(); Singleton s2=Singleton.GetInstance(); Console.WriteLine(s1==s2); //显示true
多线程下的单例模式
Singleton类(加锁):
class Singleton { private static Singleton instance; private static readonly object syncRoot = new object(); private Singleton() //构造方法为private,防止外界利用new构造函数创建此类实例的可能 { } public static Singleton GetInstance() //此方法是获取实例的唯一全局访问点 { lock (syncRoot) return instance ?? (instance = new Singleton()); //??空合并运算符,instance不为空就返回instance, //为空就创建实例instance } }
Singleton类(双重锁)
class Singleton { private static Singleton instance; private static readonly object syncRoot = new object(); private Singleton() //构造方法为private,防止外界利用new构造函数创建此类实例的可能 { } public static Singleton GetInstance() //此方法是获取实例的唯一全局访问点 { if (instance == null) { lock (syncRoot) { instance = new Singleton(); //??空合并运算符,instance不为空就返回instance, //为空就创建实例instance } } return instance; } }
静态初始化
C#提供了一种静态初始化方法,这种方法不需要编写线程安全代码,即解决多线程下的安全访问问题。静态初始化在加载时就进行实例化。
sealed class SingletonSafe { private static readonly SingletonSafe instance = new SingletonSafe(); private SingletonSafe(){} public static SingletonSafe GetInstance() { return instance; } }
应用篇
学以致用,在项目中使用单例模式。
需求背景:最近再做一个新的项目,其中有一个需求是这样的,当用户点击按钮对硬件进行操作时,首次操作,需要验证,可以十分钟内免验证。超过十分钟,就有需要验证了,只是不用再输用户名,只需输入密码即可。
解决步骤:
1、首先创建一个记录时间情况的类。用于用户操作的时间情况。
//遥控验证类 public class ControlTest { private ControlTest() { //this.IsInit = isInit; } private const int Minute = 10; //选择了确定还是取消 public bool IsInit { get; set; } //初始化时间 public DateTime? InitDateTime { get; set; }
//记录用户名 public string UserName { get; set; } //是否过时 public bool IsTimeOver { get { if (InitDateTime == null) { return true; } DateTime nowdDateTime = DateTime.Now; if (InitDateTime.Value.AddMinutes(Minute)>DateTime.Now) { return false; } return true; } } }
2、创建窗体类代码:
public partial class FrmControlLogin : DevExpress.XtraEditors.XtraForm { private static FrmControlLogin frmControlLogin; private static readonly object syncRoot = new object(); private FrmControlLogin(ControlTest controlTest) { InitializeComponent(); #region 取消函数 Action btnCancelAction = delegate { try { this.Close(); controlTest.IsInit = false; } catch (Exception ex) { throw new Exception(ex.Message); } }; #endregion #region 取消操作 this.btnCancle.Click += delegate { btnCancelAction(); }; #endregion #region 确定函数 Action btnOkAction = delegate { try { string personId = txtUserId.Text.Trim(); string password = txtPassWord.Text.Trim(); if (chkTest.Checked) { controlTest.InitDateTime = DateTime.Now; controlTest.UserName = personId; } if (!FrmControlLogonDataAccess.CheckUserPassword(personId, password)) { throw new Exception("用户名密码错误。"); } controlTest.UserName = personId; DialogResult = DialogResult.OK; controlTest.IsInit = true; } catch (Exception ex) { string errMsg = string.Format("登录失败,其原因为:\n{0}", ex.Message); CommonMethodUnit.ShowMessage(this, errMsg, "登录", MessageBoxButtons.OK, MessageBoxIcon.Information); } }; #endregion #region 确定操作 this.btnOk.Click += delegate { btnOkAction(); }; #endregion #region 接收键盘输入,ENTER键,ESC键 this.KeyPreview = true; this.KeyDown += (srcObj, keyE) => { try { switch (keyE.KeyCode) { case Keys.Enter: btnOkAction(); break; case Keys.Escape: btnCancelAction(); break; } } catch (Exception ex) { throw new Exception(ex.Message); } }; #endregion } private static FrmControlLogin GetFrmControlLogin(ControlTest controlTest) { if (frmControlLogin == null) { lock (syncRoot) { if (frmControlLogin == null) { frmControlLogin = new FrmControlLogin(controlTest); } } } frmControlLogin.txtUserId.Text = controlTest.UserName; frmControlLogin.txtPassWord.Text = ""; return frmControlLogin; } public static void ShowDoialog(ControlTest controlTest) { if (controlTest.IsTimeOver) { GetFrmControlLogin(controlTest).ShowDialog(); } if (!controlTest.IsInit) { return; } } }
其中函数GetFrmControlLogin用于创建FrmControlLogin实例,此函数通过双重锁保证线程安全:
private static FrmControlLogin GetFrmControlLogin(ControlTest controlTest) { if (frmControlLogin == null) { lock (syncRoot) { if (frmControlLogin == null) { frmControlLogin = new FrmControlLogin(controlTest); } } } frmControlLogin.txtUserId.Text = controlTest.UserName; frmControlLogin.txtPassWord.Text = ""; return frmControlLogin; }
有同事建议使用volatile关键字,减少代码量。
1、把FrmControlLogin声明为volatile。然后就可以函数GetFrmControlLogin中的锁代码去掉,依然保证线程安全。
private static volatile FrmControlLogin frmControlLogin;
2、注释掉SyncRoot
//private static readonly object syncRoot = new object();
3、修改GetFrmControlLogin函数。
private static FrmControlLogin GetFrmControlLogin(ControlTest controlTest) { if (frmControlLogin == null) { frmControlLogin = new FrmControlLogin(controlTest); } frmControlLogin.txtUserId.Text = controlTest.UserName; frmControlLogin.txtPassWord.Text = ""; return frmControlLogin; }
图片就不多传了,就传一张十分钟后,需要输密码的图片吧。
参考:《大化设计模式》 作者:程杰