工厂+单例模式
分享博客文章马上一年了,还没有设计模式方面的文章呢,因此本篇将和大家分享的是工厂模式和单例模式,这里举例的工厂模式例子很简单应该是大家常用的写法与逻辑,后续分享的文章会进一步扩展工厂模式的写法,敬请期待;这里同时也讲解常用单例模式写法,并简单提取了一个单例模式通用方法供大家使用;本章内容希望大家能够喜欢,也希望各位多多"扫码支持"和"推荐"谢谢!如果您想和我们交流更多mvc相关信息可以来Ninesky框架作者:洞庭夕照 指定的官方群:428310563交流;
» 工厂模式设计图
» 工厂模式测试用例
» 单例模式讲解
» 单例模式测试用例
下面一步一个脚印的来分享:
» 工厂模式设计图
首先,用使用一个简单的工厂模式为系统服务需要对其原理或者说流程有大概的了解,这里先通过一幅粗糙的手工图展示下流程:
看图能明显看到一个工厂池,这个工厂池作用就是如图所示来创建不同的类型的对象,而这些不同类型的对象通常有一个或一些列相似点,因此这里能提取出来一个基类(或接口),加工池创建对象后直接返回创建的子类(或实现接口的类型),通过暴露父类(接口)提供给调用方想要的对象,这种做法使得调用方在使用工厂模式的时候,不需要关注具体对象,只需要关注暴露的父类(接口)即可,这就是工厂模式的好处;
» 工厂模式测试用例
这里举例使用工厂模式的场景是:家长,老师,学生这些社会人员的一次对话;他们都具有一个特性就是说话动作(当然特殊情况忽略),名字,称呼等类似的属性,因此这里我们能够抽出一个共同的基类MoPeopleClass:
1 /// <summary> 2 /// 父类 3 /// </summary> 4 public class MoPeopleClass 5 { 6 /// <summary> 7 /// 类型 8 /// </summary> 9 public EmPeople People { get; set; } 10 11 /// <summary> 12 /// 名字 13 /// </summary> 14 public string Name { get; set; } 15 16 public MoPeopleClass(EmPeople people = EmPeople.家长) 17 { 18 this.People = people; 19 } 20 21 /// <summary> 22 /// 说话 23 /// </summary> 24 /// <param name="myName"></param> 25 /// <param name="mySayContent"></param> 26 /// <returns></returns> 27 public string Say(string mySayContent) 28 { 29 if (string.IsNullOrWhiteSpace(this.Name)) { this.Name = "匿名"; } 30 31 return string.Format("【{1}】{0} {2}:{3}<br/>", 32 this.Name, 33 this.People, 34 DateTime.Now.ToString("HH:mm:ss.fff"), 35 mySayContent); 36 } 37 }
因为只需要测试只有说话交流的动作因此这个基类默认属于家长类型,这里我定义了一个人员分类的枚举EmPeople:
1 public enum EmPeople 2 { 3 家长 = 1, 4 老师 = 2, 5 学生 = 3, 6 校长 = 4, 7 校花 = 5 8 }
然后分别定义MoTeacher(老师类),学生类(MoStudent),继承父类MoPeopleClass并继承她的带参数的构造函数:
1 /// <summary> 2 /// 老师类 3 /// </summary> 4 public class MoTeacher : MoPeopleClass 5 { 6 public MoTeacher() : base(EmPeople.老师) { } 7 8 } 9 10 /// <summary> 11 /// 学生类 12 /// </summary> 13 public class MoStudent : MoPeopleClass 14 { 15 public MoStudent() : base(EmPeople.学生) { } 16 }
再来咋们就是定义工厂类MoSimpleFactory并且定义个根据人员类型返回对应的实体方法GetPeople:
1 /// <summary> 2 /// 工厂类 3 /// </summary> 4 public class MoSimpleFactory 5 { 6 7 public MoPeopleClass GetPeople(EmPeople people) 8 { 9 switch (people) 10 { 11 case EmPeople.家长: 12 return new MoPeopleClass(); 13 case EmPeople.老师: 14 return new MoTeacher(); 15 case EmPeople.学生: 16 return new MoStudent(); 17 18 default: 19 throw new Exception("没有找到对应类型"); 20 } 21 } 22 }
看起来的确简单,那不废话了来直接看在Action中的测试代码:
1 var sbSimpleFactoryLog = new StringBuilder(string.Empty); 2 3 SimpleFactory.MoSimpleFactory simpleFactory = new SimpleFactory.MoSimpleFactory(); 4 var p1 = simpleFactory.GetPeople(FactoryPattern.SimpleFactory.EmPeople.家长); 5 p1.Name = "神牛步行1"; 6 sbSimpleFactoryLog.AppendFormat("{0}", p1.Say("老师你好")); 7 var p2 = simpleFactory.GetPeople(FactoryPattern.SimpleFactory.EmPeople.老师); 8 p2.Name = "神牛步行2"; 9 sbSimpleFactoryLog.AppendFormat("{0}", p2.Say("你好,家长")); 10 11 var p3 = simpleFactory.GetPeople(FactoryPattern.SimpleFactory.EmPeople.学生); 12 p3.Name = "神牛步行3"; 13 var say3 = p3.Say(string.Format("我是学生{0},老师您好。", p3.Name)); 14 sbSimpleFactoryLog.AppendFormat("{0}", say3); 15 ViewBag.SimpleFactoryLog = sbSimpleFactoryLog;
能看出这里通过类似代码: simpleFactory.GetPeople(FactoryPattern.SimpleFactory.EmPeople.家长) 来获取不同类型的对象,工厂模式就是这样至少需要一个参数来标记您获取对象的类型,不然无法确定你想要的对象,再来看下页面效果:
能从图上看出来我们获取到了3个不同类型的对象,并且执行了说的动作,这就是工厂模式的效果;本篇分享的是最基础的工厂模式用法,后面文章会逐步的扩展敬请期待。
» 单例模式讲解
首先,咋们需要明白单例模式的意义:系统全局只会存在一个对应的实例(通俗点就是整个系统中,对于某个类只会存在一次new的结果);由于静态变量属于静态存储方式能够被存储到内存中的静态数据区,所以通常单例模式都是用静态变量来保持实例的唯一性,单例模式常用写法有以下几种:
1. 线程不安全模式
2. 线程安全模式(使用锁来保证安全)
3. 静态初始化
4. 延迟初始化
这里还有其他的写法,但是个人觉得不怎么常用暂时忽略吧;这里以父类(MoPeopleClass)为例,贴下具体代码:
1. 线程不安全模式
1 static MoPeopleClass moPeopleClass = null; 2 /// <summary> 3 /// 调用方式MoPeopleClass.Current 4 /// </summary> 5 public static MoPeopleClass Current 6 { 7 get 8 { 9 return moPeopleClass ?? new MoPeopleClass(); 10 } 11 }
2. 线程安全模式(使用锁来保证安全)
1 static object lockPeople = new object(); 2 static MoPeopleClass moPeopleClass = null; 3 /// <summary> 4 /// 调用方式MoPeopleClass.Current 5 /// </summary> 6 public static MoPeopleClass Current 7 { 8 get 9 { 10 if (moPeopleClass == null) 11 { 12 lock (lockPeople) 13 { 14 moPeopleClass = moPeopleClass ?? new MoPeopleClass(); 15 } 16 } 17 return moPeopleClass ?? new MoPeopleClass(); 18 } 19 }
第一种和第二种差别在于后者增加了一个锁,这个锁就能避免多线程调用的时候创建多个对象,值得注意的是lock的代码里面再次通过 moPeopleClass ?? new MoPeopleClass(); 来判断了如果不为空直接返回对象,因为在lock的时候可能其他地方在创建同样的对象,因此需要再次做非空判断;
3. 静态初始化
1 static MoPeopleClass moPeopleClass = new MoPeopleClass(); 2 /// <summary> 3 /// 调用方式MoPeopleClass.Current 4 /// </summary> 5 public static MoPeopleClass Current 6 { 7 get 8 { 9 return moPeopleClass; 10 } 11 }
只能说这种方式代码实现最快,但是有个缺点是因为这里用的是static静态字符修饰的变量所以在创建实例后,会直接加载到内存中,不管您系统中是否有调用此实例,这种方式如果在对象多的话很容易造成内存问题;
4. 延迟初始化
1 /// <summary> 2 /// 父类 3 /// </summary> 4 public class MoPeopleClass 5 { 6 7 /// <summary> 8 /// 调用方式MoPeopleClass.Current 9 /// </summary> 10 public static MoPeopleClass Current 11 { 12 get 13 { 14 return MoPeopleExtend.moPeopleClass; 15 } 16 } 17 //内部内来创建对象 18 private class MoPeopleExtend 19 { 20 internal static readonly MoPeopleClass moPeopleClass = new MoPeopleClass(); 21 } 22 }
这种方式使用内部类的方式创建实例;
» 单例模式测试用例
下面我这里使用线程安全方式来创建单例,我这里封装了一个这种类型的通用方法,代码如下:
1 /// <summary> 2 /// 单例模式 3 /// </summary> 4 /// <typeparam name="T"></typeparam> 5 public class Singleton<T> where T : class,new() 6 { 7 static object lockt = new object(); 8 static T t = null; 9 /// <summary> 10 /// 加锁单例 11 /// </summary> 12 public static T Current 13 { 14 get 15 { 16 if (t == null) 17 { 18 lock (lockt) 19 { 20 t = t ?? new T(); 21 } 22 } 23 return t; 24 } 25 } 26 27 /// <summary> 28 /// 不加锁单例 29 /// </summary> 30 public static T CurrentUnLock 31 { 32 get 33 { 34 if (t == null) 35 { 36 t = t ?? new T(); 37 } 38 return t; 39 } 40 } 41 } 42 }
然后为了测试我增加枚举类型:
1 public enum EmPeople 2 { 3 家长 = 1, 4 老师 = 2, 5 学生 = 3, 6 校长 = 4, 7 校花 = 5 8 }
因为新增的“校长”和“校花”通常咋们认定一个学校就一人吧哈哈,那这个时候单例就有用武之地了;修改工厂类代码如下:
1 /// <summary> 2 /// 工厂类 3 /// </summary> 4 public class MoSimpleFactory 5 { 6 7 public MoPeopleClass GetPeople(EmPeople people) 8 { 9 switch (people) 10 { 11 case EmPeople.家长: 12 return new MoPeopleClass(); 13 //case EmPeople.老师: 14 // return new MoTeacher(); 15 //case EmPeople.学生: 16 // return new MoStudent(); 17 case EmPeople.校长: 18 return ServiceCollection.Singleton<MoHeadeMaster>.Current; 19 case EmPeople.校花: 20 return ServiceCollection.Singleton<MoStudent>.Current; 21 } 22 } 23 24 }
注意为了方便我直接在工厂里面只用了刚才通用的单例方法: ServiceCollection.Singleton<T> ,然后为了测试我们同时调用两次工厂类创建两次校长的对象和两次校花的对象:
1 var SingltonLog = new StringBuilder(string.Empty); 2 var p0 = simpleFactory.GetPeople(FactoryPattern.SimpleFactory.EmPeople.校长); 3 p0.Name = "神牛步行0"; 4 SingltonLog.AppendFormat("{0}", p0.Say("我是校长。")); 5 var p00 = simpleFactory.GetPeople(FactoryPattern.SimpleFactory.EmPeople.校长); 6 SingltonLog.AppendFormat("{0}", p00.Say("我是校长!")); 7 8 var p6 = simpleFactory.GetPeople(FactoryPattern.SimpleFactory.EmPeople.校花); 9 p6.Name = "神牛步行6"; 10 SingltonLog.AppendFormat("{0}", p6.Say("我是校花。")); 11 var p66 = simpleFactory.GetPeople(FactoryPattern.SimpleFactory.EmPeople.校花); 12 SingltonLog.AppendFormat("{0}", p66.Say("我是校花!")); 13 14 ViewBag.SingltonLog = SingltonLog;
注意了这里第一次调用工厂生成校长实例后,我给校长赋了名称,但是第二次没有,校花也是如此,通常来说第二次没有赋名称应该会显示“匿名”,但由于这里用了单例,在第一次实例化校长(或校花)后我赋值了名称,第二次获取的校长(校花)其实和第一次是同一个实例独享(通俗点就是同一内存地址),因此第二次显示的名称应该和第一次的一样才对;咋们运行看效果:
从效果图能看出咋们的单例模式成功了,为了进一步验证单例的存在,我们重新创建一个Action和试图并录入如下代码:

1 public ActionResult FatoryPatternTest() 2 { 3 var SingltonLog = new StringBuilder(string.Empty); 4 5 SimpleFactory.MoSimpleFactory SimpleFactory = new SimpleFactory.MoSimpleFactory(); 6 var p00 = SimpleFactory.GetPeople(FactoryPattern.SimpleFactory.EmPeople.校长); 7 SingltonLog.AppendFormat("{0}", p00.Say("我是校长!")); 8 var p66 = SimpleFactory.GetPeople(FactoryPattern.SimpleFactory.EmPeople.校花); 9 SingltonLog.AppendFormat("{0}", p66.Say("我是校花!")); 10 11 ViewBag.SingltonLog = SingltonLog; 12 13 return View(); 14 }
此时我们并没有赋值昵称属性,按照单例模式的说明这个时候由于前一个页面有赋值名称,这个测试新页面应该也会显示名称才是真确的:
和咋们预想的情况一样,这就是单例模式;好了本次分享到这里就结束了,马上快到3.8妇女节了,如果你想给你的她或者自己买件衣服或者裙子,不妨来这里看看服装店:神牛衣柜3,谢谢你的支持。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步