单例模式(20)
今天我们来讲一下单例模式,下面我们来用winform来做一个简单的展示,就是点击一个菜单,弹出另一个窗体(做成父子窗体的形式)。
建一个窗体(父窗体),拖一个MenuStrip,再建一个窗体(子窗体)。
然后:
1 private void Form1_Load(object sender, System.EventArgs e) 2 { 3 this.IsMdiContainer = true; 4 } 5 6 private void 工具栏ToolStripMenuItem_Click(object sender, System.EventArgs e) 7 { 8 Form2 form2 = new Form2(); 9 form2.MdiParent = this; 10 form2.Show(); 11 }
现在,我们看一下执行结果。
我们可以看到,每次我们点击一下工具,都会弹出一个新的窗体,我们想要的结果是:之弹出一次这个窗体就行。
有些伙伴说,可以用ShowDialog() 啊,在此,说明一下,在父子窗体中,子窗体是不能用ShowDialog()出来的,再退一步讲,即便是能用ShowDialog(),但是这是一个阻塞机制,很不灵活。
那么,为了实现我们想要的结果,我们该如何做呢?
简单啊,我们只需要修改一下点击事件里的代码就可以了。
先声明一个子窗体的全局变量,然后修改一下代码:
1 private Form2 form2; 2 private void Form1_Load(object sender, System.EventArgs e) 3 { 4 this.IsMdiContainer = true; 5 } 6 7 private void 工具栏ToolStripMenuItem_Click(object sender, System.EventArgs e) 8 { 9 if (form2 == null) 10 { 11 form2 = new Form2(); 12 form2.MdiParent = this; 13 form2.Show(); 14 } 15 }
这样就可以实现我们想要的结果了,这样就完了嘛?
这里存在两个问题:
1、如果我有很多按钮,每个按钮都想弹出这个子窗体,想实现这个结果,需要复制粘贴这些代码,显然是很失败的做法。
2、上述结果,如果我关闭了打开的窗体,我在点击工具菜单,则不会再弹出窗体来了。(因为关闭窗体后,该窗体仅仅是Disposed了,但对象还不是null,所以判断是否为null是有一定的问题的。)
针对上述两个问题,我们来改进一下。就用到今天要讲的单例模式了,好,我们来看一下如何实现。
Form2中的代码
1 public partial class Form2 : Form 2 { 3 //声明一个静态的类变量 4 private static Form2 form2 = null; 5 //构造方法私有,外部代码不能直接new来实例化它 6 private Form2() 7 { 8 InitializeComponent(); 9 } 10 //得到类实例的方法,返回值就是本类对象,注意也是静态的。 11 public static Form2 GetForm() 12 { 13 //当内部的form2是null或者被Dispose过,则new它 14 //并且设计其MdiParent为Form1,此时将实例化的对象存在静态的变量form2中,以后就可以不用实例化而得到它了 15 if (form2 == null || form2.IsDisposed) 16 { 17 form2 = new Form2(); 18 form2.MdiParent = Form1.ActiveForm; 19 } 20 return form2; 21 } 22 }
Form1 中的调用
1 public partial class Form1 : Form 2 { 3 4 public Form1() 5 { 6 InitializeComponent(); 7 } 8 9 private void Form1_Load(object sender, System.EventArgs e) 10 { 11 this.IsMdiContainer = true; 12 } 13 private void 工具栏ToolStripMenuItem_Click(object sender, System.EventArgs e) 14 { 15 Form2.GetForm().Show(); 16 } 17 }
这样,就达到了我们想要的效果了。
我们来总结一下:
单例模式:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
通常我们剋让一个全局变量是的一个对象被访问,但它不能防止你实例化多个对象。最好的办法就是,让类自身负责保存它的唯一实例,这个类可以保证没有其他实例可以被创建,并且他可以提供一个访问该实例的方法。
好,我们来写一下单例模式的代码
1 class Singleton 2 { 3 private static Singleton instance; 4 //构造方法让其private,这就堵死了外界利用用new创建次类实例的可能 5 private Singleton() 6 { 7 8 } 9 //此方法是获得本类实例的唯一全局访问点 10 public static Singleton GetInstance() 11 { 12 //若实例不存在,则new一个新实例,否则返回已有的实例 13 if (instance==null) 14 { 15 instance = new Singleton(); 16 } 17 return instance; 18 } 19 }
客户端:
1 public static void Main() 2 { 3 Singleton s1 = Singleton.GetInstance(); 4 Singleton s2 = Singleton.GetInstance(); 5 //比较两次实例化后对象的结果是否相同 6 if (s1==s2) 7 { 8 Console.WriteLine("两个对象是相同的实例。"); 9 } 10 Console.ReadKey(); 11 }
另外还有一个问题,如果多线程中,多个线程同时访问Singleton类,调用GetInstance()方法,会有可能创造多个实例的。所以,我们可以用lock进行处理一下。
好,我们来看用lock处理后的Singleton类
1 class Singleton 2 { 3 private static Singleton instance; 4 //程序运行时创建一个静态只读的进程辅助对象 5 private static readonly object syncRoot = new object(); 6 //构造方法让其private,这就堵死了外界利用用new创建次类实例的可能 7 private Singleton() 8 { 9 10 } 11 //此方法是获得本类实例的唯一全局访问点 12 public static Singleton GetInstance() 13 { 14 //在同一个时刻加了锁的那部分程序只有一个线程可以进入 15 lock (syncRoot) 16 { 17 //若实例不存在,则new一个新实例,否则返回已有的实例 18 if (instance == null) 19 { 20 instance = new Singleton(); 21 } 22 } 23 return instance; 24 } 25 }
对于上述的代码,小伙伴们发现了一个问题没有,就是不管instance是不是为null,都会先加锁,这势必会影响性能的,好我们来看一下优化后的:
1 class Singleton 2 { 3 private static Singleton instance; 4 //程序运行时创建一个静态只读的进程辅助对象 5 private static readonly object syncRoot = new object(); 6 //构造方法让其private,这就堵死了外界利用用new创建次类实例的可能 7 private Singleton() 8 { 9 10 } 11 //此方法是获得本类实例的唯一全局访问点 12 public static Singleton GetInstance() 13 { 14 //先判断实例是否存在,如果不存在再加锁处理 15 if (instance==null) 16 { 17 //在同一个时刻加了锁的那部分程序只有一个线程可以进入 18 lock (syncRoot) 19 { 20 //若实例不存在,则new一个新实例,否则返回已有的实例 21 if (instance == null) 22 { 23 instance = new Singleton(); 24 } 25 } 26 } 27 return instance; 28 } 29 }
其实再实际应用当中,C#与用功语言运行库也提供了一种“静态初始化”方法,这种方法不需要开发人员显示的编写线程安全代码,即可解决多线程环境下他是不安全的问题。
好,下面我们来看一下“静态初始化”方法的单例模式
1 //sealed阻止发生派生,而派生可能会增加实例 2 public sealed class Singleton 3 { 4 //在第一次引用类的任何成员时创建实例,共功与原运行库负责处理变量初始化。 5 private static readonly Singleton instance = new Singleton(); 6 private Singleton() 7 { 8 } 9 10 public static Singleton GetInstance() 11 { 12 return instance; 13 } 14 }
这种静态初始化的方式是自己被加载时就将自己实例化,所以被形象的成为恶汉式单例类,原先的单例模式处理是要再第一次被引用时,才会将自己实例化,所以被称为懒汉单例类。
好,单例模式我们就介绍完了,下一篇博文我们讲 桥接模式
本系列将持续更新,喜欢的小伙伴可以点一下关注和推荐,谢谢大家的支持