设计模式之创建型设计模式

单例模式

 单例模式就是整个程序中有且仅有一个实例,该类负责创建自己的对象,并且保证只有一个对象被创建。

 主要有三个步骤:私有化构造函数;创建一个公开的静态方法给外界提供实例;提供一个静态变量重用

比如:

  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();
                   }

创建型设计模式的核心套路,就是管理对象创建 

工厂模式详解

原文地址:https://www.cnblogs.com/anjingdian/p/15368097.html