设计模式之创建型设计模式
单例模式
单例模式就是整个程序中有且仅有一个实例,该类负责创建自己的对象,并且保证只有一个对象被创建。
主要有三个步骤:私有化构造函数;创建一个公开的静态方法给外界提供实例;提供一个静态变量重用
比如:
public class Singleton { /// <summary> /// 构造函数耗时耗资源 /// 1 私有化构造函数 /// </summary> private Singleton()//加个参数 防止反射破坏单例,因为反射调用私有构造函数的方法是没法传参数的 { //Activator.CreateInstance(null, true); long lResult = 0; for (int i = 0; i < 10000000; i++) { lResult += i; } Thread.Sleep(2000); Console.WriteLine($"{this.GetType().Name}被构造一次 {Thread.CurrentThread.ManagedThreadId}"); } /// <summary> /// 3 提供一个静态变量重用 /// </summary> private static Singleton _Singleton = null; /// <summary> /// 2 公开的静态方法提供实例 /// </summary> /// <returns></returns> public static Singleton CreateInstance() { if (_Singleton == null)//是在对象初始化之后,可以并发了 { _Singleton = new Singleton(); } return _Singleton; } }
但是现在的代码是不安全的,比如说在多线程下,如果此时有多个线程访问if(_Singleton==null),而且此时实例还没有创建,那么后续就会创建多个实例,就不是单例模式了。
可以加一个lock:
private static readonly object Singleton_Lock = new object(); /// <summary> /// 2 公开的静态方法提供实例 /// </summary> /// <returns></returns> public static Singleton CreateInstance() { if (_Singleton == null)//不加这个的话,多个线程来的时候都被锁住,就算是多线程那也实现不了并发,和单线程没区别,加了这个判断后面的子线程不走lock,直接返回,可以体现多线程的优点 { lock (Singleton_Lock)//反多线程--限制并发的 { if (_Singleton == null) { _Singleton = new Singleton(); } } } return _Singleton; }
调用:
for (int i = 0; i < 1000; i++) { Task.Run(() =>//5个线程并发执行 { Singleton singleton = Singleton.CreateInstance(); singleton.Show(); }); }
这就是双判断锁的经典写法,懒汉式实现(就是说不主动调用创建实例的方法就不会创建这个类的实例对象)。
下面是通过静态构造函数实现的饿汉式方法:
public sealed class SingletonSecond { /// <summary> /// 构造函数耗时耗资源 /// 1 私有化构造函数 /// </summary> private SingletonSecond() { long lResult = 0; for (int i = 0; i < 10000000; i++) { lResult += i; } Thread.Sleep(2000); Console.WriteLine($"{this.GetType().Name}被构造一次 {Thread.CurrentThread.ManagedThreadId}"); } /// <summary> /// 3 提供一个静态变量重用 /// </summary> private static SingletonSecond _SingletonSecond = null; /// <summary> /// 静态构造函数:CLR调用,在对象使用前完成初始化且只执行一次 /// </summary> static SingletonSecond() { _SingletonSecond = new SingletonSecond(); } /// <summary> /// 2 公开的静态方法提供实例 /// </summary> /// <returns></returns> public static SingletonSecond CreateInstance() { return _SingletonSecond; } }
在类中,静态构造函数,静态字段都是先于方法的,普通字段和普通构造方法也是如此,执行类中的方法之前肯定都是要先执行他们的。
所以,也可以通过静态字段来实现:
public class SingletonThird { /// <summary> /// 构造函数耗时耗资源 /// 1 私有化构造函数 /// </summary> private SingletonThird() { long lResult = 0; for (int i = 0; i < 10000000; i++) { lResult += i; } Thread.Sleep(2000); Console.WriteLine($"{this.GetType().Name}被构造一次 {Thread.CurrentThread.ManagedThreadId}"); } /// <summary> /// 3 提供一个静态变量重用 /// 静态字段:CLR保障,在使用类型之前完成初始化,且只初始化一次 /// </summary> private static SingletonThird _SingletonThird = new SingletonThird(); /// <summary> /// 2 公开的静态方法提供实例 /// </summary> /// <returns></returns> public static SingletonThird CreateInstance() { return _SingletonThird; } }
下面看这样一段代码:
public class Singleton { /// <summary> /// 构造函数耗时耗资源 /// 1 私有化构造函数 /// </summary> private Singleton()//加个参数 防止反射 { //Activator.CreateInstance(null, true); long lResult = 0; for (int i = 0; i < 10000000; i++) { lResult += i; } Thread.Sleep(2000); Console.WriteLine($"{this.GetType().Name}被构造一次 {Thread.CurrentThread.ManagedThreadId}"); } /// <summary> /// 3 提供一个静态变量重用 /// </summary> private static Singleton _Singleton = null; private static readonly object Singleton_Lock = new object(); /// <summary> /// 2 公开的静态方法提供实例 /// </summary> /// <returns></returns> public static Singleton CreateInstance() { if (_Singleton == null)//是在对象初始化之后,可以并发了 { lock (Singleton_Lock)//反多线程--限制并发的 { if (_Singleton == null) { _Singleton = new Singleton(); } } } return _Singleton; } //既然是单例,大家用的是同一个对象,用的是同一个方法,那还会并发吗 还有线程安全问题吗? public int iTotal = 0; public void Show() { this.iTotal++; } }
调用:
for (int i = 0; i < 1000; i++) { Task.Run(() => { Singleton singleton = Singleton.CreateInstance(); singleton.Show(); }); } Thread.Sleep(5000); Singleton singleton = Singleton.CreateInstance(); Console.WriteLine(singleton.iTotal);
最后输出的结果会是多少呢?答案是不一定。
因为单例和单线程是没关系的,Show方法没有锁,多线程并发下不一定是多少,在1到1000区间中,包括1和1000。但是不会大于1000或者等于0,单例模式表明了自始至终都只有一个实例,也就是只初始化了一次,就算iTotal不是静态字段,也不会因为多次调用Show方法而被初始化为0,。
改成下面就是正常了:
public int iTotal = 0; public void Show() { //加锁 lock (Singleton_Lock) { this.iTotal++; } }
为什么要使用单例模式
首先要知道单例模式不是什么太好的东西,一旦使用了,这个静态对象会一直存储在程序中的。请不要画蛇添足,没有必须单例的,请勿单例。
如果说在这个进程中只需要一个的,那就可以用单例,比如线程池,数据库连接池,配置文件对象(如果确认维护的数据不会更改的话可以单例)
原型模式
跟单例的区别就是新的实例,不是同一个,就不会互相影响。快速复制对象,不走构造函数---浅克隆。
namespace OOP.DesignerPattern.SingletonPattern { public class SingletonPrototype { /// <summary> /// 构造函数耗时耗资源 /// 1 私有化构造函数 /// </summary> private SingletonPrototype() { long lResult = 0; for (int i = 0; i < 10000000; i++) { lResult += i; } Thread.Sleep(2000); Console.WriteLine($"{this.GetType().Name}被构造一次 {Thread.CurrentThread.ManagedThreadId}"); } /// <summary> /// 3 提供一个静态变量重用 /// </summary> private static SingletonPrototype _SingletonPrototype = new SingletonPrototype(); /// <summary> /// 2 公开的静态方法提供实例 /// </summary> /// <returns></returns> public static SingletonPrototype CreateInstance() { SingletonPrototype prototype = (SingletonPrototype)_SingletonPrototype.MemberwiseClone();//内存copy //跟单例的区别就是 新的实例 不是同一个,就不会互相影响 //快速复制对象,不走构造函数---浅克隆 return prototype; } //既然是单例,大家用的是同一个对象,用的是同一个方法,那还会并发吗 还有线程安全问题吗? public int iTotal = 0; public void Show() { this.iTotal++; } } }
此时iTotal是多少?
for (int i = 0; i < 1; i++) { Task.Run(() =>//5个线程并发执行 { SingletonPrototype singleton = SingletonPrototype.CreateInstance(); singleton.Show(); }); } Thread.Sleep(1000); SingletonPrototype singleton = SingletonPrototype.CreateInstance(); Console.WriteLine(singleton.iTotal);
答案是0;因为CreateInstance方法之后copy出了一个新的实例,和之前没有任何关系,所以就是iTotal的默认值0。
什么时候用?
比如说构造函数中可能耗时间比较多,那可以不用单例直接用原型模式。
简单工厂模式
就是为了转移对象的创建(就是甩锅,把对象的创建转移到专门的类中),就是把对象都放到一起创建。转移了矛盾,但是没有消除矛盾,甚至集中了矛盾。
看下面的代码:
namespace FactoryPattern.War3.Interface { public interface IRace { /// <summary> /// show出王者 /// </summary> void ShowKing(); } }
下面是游戏中不同种族的信息:
/// <summary> /// War3种族之一 /// </summary> public class Undead : IRace { public void ShowKing() { Console.WriteLine("The King of {0} is {1}", this.GetType().Name, "GoStop"); } } /// <summary> /// War3种族之一 /// </summary> public class ORC : IRace { public void ShowKing() { Console.WriteLine("The King of {0} is {1}", this.GetType().Name, "Grubby"); } } /// <summary> /// War3种族之一 /// </summary> public class NE : IRace { public void ShowKing() { Console.WriteLine("The King of {0} is {1}", this.GetType().Name, "Moon"); } } /// <summary> /// War3种族之一 /// </summary> public class Human : IRace { public Human(int id, DateTime dateTime, string reamrk) { } public Human() { } public void ShowKing() { Console.WriteLine("The King of {0} is {1}", this.GetType().Name, "Sky"); } }
正常情况下调用:
IRace iRace = new Human();//面向抽象 iRace.ShowKing();
但是如果使用简单抽象工厂模式,将对象的创建都集中起来,通过设置一些枚举类型(其它符合业务的方式也都可以)来创建不同的对象:
/// <summary> /// 简单工厂转移矛盾,但是没有消除矛盾 /// 而且还集中了矛盾 /// </summary> public class SimpleFactory { public static IRace CreateInstance(RaceType raceType) { IRace iRace = null; switch (raceType) { case RaceType.Human: iRace = new Human(); break; case RaceType.Undead: iRace = new Undead(); break; case RaceType.NE: iRace = new NE(); break; case RaceType.ORC: iRace = new ORC(); break; default: throw new Exception("wrong raceType"); } return iRace; } public enum RaceType { Human, Undead, NE, ORC } }
调用:
{ IRace iRace = SimpleFactory.CreateInstance(SimpleFactory.RaceType.Human);// new Human();//怎么样更面向抽象 iRace.ShowKing(); }
就是很简单,简单到23种设计模式中都没有它,就是转移了矛盾,集中了起来。但是必须要说的是,简单工厂模式的确比之前的写法要高级,甚至减少了耦合,比如之前写IRace iRace=new Hunman(),这是直接和Human这个类耦合了万一哪天Human类中没有无参构造函数了,那这个写法就报错了,就需要到调用类中来改代码,需要改动业务逻辑这里,使用简单工厂模式之后,就把矛盾问题转移到了工厂类中,不改动业务逻辑的类,减少了风险,降低了耦合。
当然,现在是需要传入枚举信息然后返回对象的,但是我们也可以通过配置文件进行配置,根据实际业务来选择:
private static string IRaceConfigString = System.Configuration.ConfigurationManager.AppSettings["IRaceConfig"]; /// <summary> /// 可配置 /// </summary> /// <returns></returns> public static IRace CreateInstanceConfig() { RaceType raceType = (RaceType)Enum.Parse(typeof(RaceType), IRaceConfigString); return CreateInstance(raceType); }
配置文件:
<appSettings> <add key="IRaceConfig" value="Human"/> </appSettings>
调用:
{ //可配置 IRace iRace = SimpleFactory.CreateInstanceConfig(); iRace.ShowKing(); }
如果你不想new,但是又想获取对象实例,可以通过反射来实现:
//如果你不想new,但是又想获取对象实例,有哪几种方法? private static string IRaceConfigReflectionString = System.Configuration.ConfigurationManager.AppSettings["IRaceConfigReflection"]; public static IRace CreateInstanceConfigReflection() { Assembly assembly = Assembly.Load(IRaceConfigReflectionString.Split(',')[1]); Type type = assembly.GetType(IRaceConfigReflectionString.Split(',')[0]); return (IRace)Activator.CreateInstance(type); }
配置文件中:
<appSettings> <add key="IRaceConfigReflection" value="FactoryPattern.War3.Service.Human,FactoryPattern.War3.Service"/> </appSettings>
工厂方法模式(工厂模式)
上面简单工厂将矛盾转移,集中了矛盾,但是没有消除矛盾(没有使用反射的前提下)。所以我们可以通过工厂方法模式来消除集中的矛盾,工厂方法模式能够屏蔽细节,进行扩展。
我们给游戏中每一个种族都创建一个工厂类,具体的对象创建细节都在对应的种族工厂中实现,上层只是调用获得对应的对象信息,至于怎么创建,创建过程中需要哪些信息,都交给工厂来处理。比如我们需要一个人族,那就调用人族工厂开放出来的接口,具体的实现交给工厂解决,细节不开放给上层。
例如:
public interface IFactory { IRace CreateInstance(); } public class HumanFactory : IFactory { public virtual IRace CreateInstance() { //IRace iRace = new Human(); IRace iRace = new Human(123, DateTime.Now, "123"); return iRace; } } public class UndeadFactory : IFactory { public IRace CreateInstance() { IRace iRace = new Undead(); return iRace; } }
调用:
{ IFactory factory = new HumanFactory(); //就是为了扩展(mvc扩展IOC就知道了) 为了屏蔽细节 IRace race = factory.CreateInstance(); }
比如说后期制造不同种族的工艺可能有需要改进的地方,那就在子类工厂中实现,旧工艺可能也还会有用到的地方,所以留着,只需要在逻辑层(上层)调用的时候做更改就可以。这就是证明了工厂方法模式也有着扩展能力的一方面。
namespace OOP.DesignerPattern.ThreeFactory.FactoryMethod { public class HumanFactory : IFactory { /// <summary> /// 实现接口,并且设置为虚方法,继承类可重写 /// </summary> /// <returns></returns> public virtual IRace CreateInstance() { //IRace iRace = new Human(); IRace iRace = new Human(123, DateTime.Now, "123"); return iRace; } } /// <summary> /// 继承父类并重写,后期可能有需要改进的地方,那就在子类工厂中实现,旧工艺可能也还会有用到的地方,所以留着,只需要在逻辑层(上层)调用的时候做更改就可以。 /// </summary> public class HumanFactoryChild : HumanFactory { public override IRace CreateInstance() { Console.WriteLine("12345667.."); //IRace iRace = new Human(); IRace iRace = new Human(123, DateTime.Now, "123"); return iRace; } } }
调用:
{ IFactory factory = new HumanFactory(); //就是为了扩展(mvc扩展IOC就知道了) 为了屏蔽细节 IRace race = factory.CreateInstance(); IFactory factory1 = new HumanFactoryChild(); IRace race1 = factory1.CreateInstance(); }
抽象工厂模式
在工厂方法模式的基础加了一个抽象的约束(一定要注意,抽象方法是没有实现主体的,所以必须要在继承类中实现,不然编译不过去,所以在这里加了继承抽象类的约束,就必须要实现基类中的所有抽象方法,虚方法就不这样)。需要明确的一点是这样的话最好不要去扩展产品簇( 抽象工厂类中定好的对象)在这里可以理解为尽量不要扩展抽象工厂类中定好的对象,一旦增加,对应的继承类也都要增加。
比如说,我们生成一个种族,这个种族会有很多能力,比如 使用武器,采集资源等,那我们就放到一个工厂中进行处理:
抽象约束:
public abstract class AbstractFactoryBase { public abstract IRace CreatRace(); public abstract IArmy CreateArmy(); public abstract IResource CreateResource(); }
实现工厂类:
public class UndeadFactoryAbstract : AbstractFactoryBase { public override IRace CreatRace() { return new Undead(); } public override IArmy CreateArmy() { return new UndeadArmy(); } public override IResource CreateResource() { return new UndeadResource(); } } public class HumanFactoryAbstract : AbstractFactoryBase { public override IRace CreatRace() { return new Human(); } public override IArmy CreateArmy() { return new HumanArmy(); } public override IResource CreateResource() { return new HumanResource(); } }
调用:
{ //工厂方法+ 抽象--是必须全部实现的:方便扩展种族 但是不能扩展产品簇--倾斜性可扩展性设计 AbstractFactoryBase factory = new HumanFactoryAbstract(); IRace race = factory.CreatRace(); IArmy army = factory.CreateArmy(); IResource resource = factory.CreateResource(); }
创建型设计模式的核心套路,就是管理对象创建