简介
今天跟大家分享的是23种模式中的创建型模式(抽象工厂模式、工厂方法模式、单例模式、原型模式、建造者模式)和简单工厂模式,其中简单工厂模式又叫做静态工厂方法模式,与工厂方法和抽象工厂合称为工厂三姐妹。其中比较特殊的地方是,简单工厂不属于这23种模式中 的一种。
一、什么是创建型模式
创建型模式是处理对象创建的设计模式,它主要分为两个模块:一是将系统使用的具体类封装起来,二是隐藏具体类的实例创建和结合的方式。
二、创建型模式思维导图
三、工厂三姐妹(简单工厂、工厂方法、抽象工厂)
工厂模式有三种,也就是工厂三姐妹;其目的总得来说都是使得代码可复用、可维护、可灵活、可扩展,都用到了面向对象的三大特征:封装、多态、继承
1,简单工厂
包含了必要的逻辑判断,根据客户端的需求动态实例化相关的类,对客户端来说,去除了产品的具体依赖,其中不符合开放-封闭原则
结构图
工厂类
public class OperationFactory
{
public static Operation createOperate(string operate)
{
Operation oper = null;
switch (operate)
{
case "+":
oper = new OperationAdd();
break;
case "-":
oper = new Operationsub();
break;
case "*":
oper = new OperationMul();
break;
case "/":
oper = new OperationDiv();
break;
}
return oper;
}
}
客户端类
Operation oper;
oper = OperationFactory.createOperate("+");
oper.NumberA = 1;
oper.NumberB = 2;
double result = oper.GetResult();
Console.WriteLine("结果是:" + result);
Console.ReadKey();
2,工厂方法
定义:工厂方法模式是定义一个用于创建对象的接口,让子类决定实例化哪一类。工厂方法使一个类的实例化延迟到其子类。
用法:为了解决简单工厂违背的开放-封闭原则,从而引出来的。工厂方法把简单工厂的内部逻辑判断移到了客户端代码来进行。你想要加功能,本来是改工厂类的,而现在是修改客户端!它是根据依赖倒转原则,把工厂类抽象出一个接口,这个接口只有一个方法,就是创建抽象产品的工厂方法
优缺点:克服了简单工厂违背开放-封闭原则的缺点,又保持了封装对象创建过程的优点。需要更换对象时,不需要做很大的改动,降低了客户程序与产品对象的耦合。但缺点是每增加一个产品工厂的类,就会增加额外的开发量。
结构图
接口
interface IFactory
{
Operation CreateOperation();
}
乘法工厂实现接口
class MulFactory : IFactory
{
public Operation CreateOperation()
{
return new OperationMul();
}
}
客户端
IFactory operFactory = new MulFactory();
Operation oper = operFactory.CreateOperation();
oper.NumberA = 1;
oper.NumberB = 2;
double result = oper.GetResult();
Console.WriteLine("结果是:" + result);
Console.ReadKey();
3,抽象工厂
定义:提供一个创建一系列相关或相互依赖对象的接口,而无需指定他们具体的类。
用法:解决涉及到多个产品系列的问题
优缺点:最大的好处是便是易于交换产品系列,由于具体工厂类,例如IFactory factory=new AccessFactory(),在一个应用中只需要在初始化的时候出现一次,这就使得改变一个应用的具体工厂变得非常容易,只需要改变具体工厂即可使用不同的产品配置。第二大好处就是,它让具体的创建实例过程与客户端分离,客户端是通过他们的抽象接口操纵实例,产品的具体类名也被具体工厂的实现分离,符合开放-封闭和依赖倒转原则
但其缺点是,每增加一个功能,所增加的类多,从而会造成大批量的更改。所以引出了反射
反射的格式为:Assembly.load(“程序集名称”).CreateInstance(“命名空间.类名称”),**只要在程序顶端写上using System.Reflection;来引用Reflection就可以使用反射来帮助客服抽象工厂模式的先天不足了。**用反射+抽象工厂模解决了数据库访问时的可维护、可拓展的问题。从这个角度上说,所有在简单工厂的地方,都可以考虑用反射技术去除switch或if,解除分支判断带来的耦合。
结构图
//常规的写法
IUser result = new SqlserverUser();
//反射的写法
IUser result = (IUser)Assembly.Load("抽象工厂模式").CreateInstance("抽象工厂模式.SqlserverUser"); //其中得先在命名空间引用System.Reflection
四、单例模式
定义: 保证一个类仅有一个实例,并提供一个访问它的全局访问点。
个人理解,在实现唯一实例化的时候,通常我们可以让一个全局变量使得一个对象被访问,但它不能防止你实例化多个对象。一个最好的办法就是,让类自身负责保存它的唯一实例。这个类可以保证没有其他实例可以被创建,并且可以提供一个访问该实例的方法,如书中所指的提供的GetInstance方法,之前声明的全局变量private FormTloolbox ftb;
与实用类的区别
相同点:实用类通常也会采用私有化的构造方法来避免其有实例。
不同点:实用类不保存状态,不能继承多态,只是一些方法属性的集合;单例有状态,虽然唯一,反可以有子类来继承。有唯一的对象实例。实用类得例子:.Net框架里得Math类,没用过,目前体会不到
结构图
Singleton类
class Singleton
{
private static Singleton instance;
private Singleton()
{
}
public static Singleton GetInstance()
{
if (instance==null)
{
instance = new Singleton();
}
return instance;
}
}
客户端
Singleton s1 = Singleton.GetInstance();
Singleton s2 = Singleton.GetInstance();
if (s1==s2)
{
Console.WriteLine("两个对象");
}
Console.Read();
拓展一
在多线程的程序中,多个线程同时,同时访问Singleton类,调用GetInstance()方法,会有可能造成创建多个实例问题,这是可给进程加一把锁来出来,lock是确保当一个线程位于代码的临界区时,另一个线程不进入临界区。如果其他线程试图进入锁定的代码,则它将一直等待,直到该对象被释放。 为了解决让线程没错都加锁的问题,我们可用双重锁定(Double-Check Locking) ,这样的话,我们只需在实例未创建的时候再加锁出处理。同时也能保证多线程的安全。
双重锁定:进行了两次判断instance实例是否存在,如果没有第二层不能达到单例的目的,大家深思
public static Singleton GetInstance()
{
if (instance==null)
{
lock (syncRoot)
{
if (instance == null)
{
instance = new Singleton();
}
}
}
return instance;
}
拓展二
静态初始化,被称为饿汉式单例类,它的方式是自己在被加载时就将自己实例化。上述所示的单例模式,是在第一次被引用时,才会将自己实例化,称之为懒汉式单例类,它需要做双重锁定的处理才能保证安全。
它是C#与公共语言运行库提供的一种不需要开发人员显示地编写线程安安全代码,即可解决多线程环境下它是不安全的问题。解决了单例模式试图解决的两个基本问题:全局访问和实例化控制,不同之处在于它依赖公共语言运行库来初始化变量。构造方法是私有,不能再类本身以外实例化Singletion类,所以变量引用的是系统中存在的唯一实例。
静态初始化
public sealed class Singleton
{
private static readonly Singleton instance = new Singleton();//instance变量标记为readonly,意味着只能在静态初始化期间或在类构造函数中分配变量
private Singleton()
{
}
public static Singleton GetInstance()
{
return instance;
}
}
五、原型模式
定义:用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对相关。
个人理解,其实就是从一个对象再创建另外一个可定制的对象,而且不需知道任何创建的细节,也就是说通过创建一份来创建其他多份,并且可不知道其他多份具体是怎么被创建的。
说白了也就是克隆 的意思,原型模式用到了clone(),在.NET在命名空间中提供了ICloneable接口,其中就是唯一的一个方法Clone(),这样就只需要实现这个接口就可以完成原型模式了。
用处:一般在初始化的信息不发生变化的情况下,克隆是最好的办法,既隐藏了对象创建的细节,又对性能是大大的提高。它等于是不用重复新初始化对象,而是动态地获得对象运行时的状态。
拓展:又由于在现实生活中,如书上所说的,会再有一个工作经历类。这就涉及到克隆(clone)克隆又涉及浅复制和深复制。浅复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他的对象的引用都仍然指向原来的对象。没有达到复制时就一变二,二变三的效果,如本书中的例子,三次都设置了不同的工作经历,但却显示了最后一次的设置。如果有深复制,则可避免这样的效果。深复制把引用对象的变量指向复制过的新对象,而不是原有的被引用的对象。
其中MemberwiseClon()方法是浅表复制,如果字段是值类型的,则对该字段执行逐位复制,如果是引用类型,则复制引用但不复制引用的对象;也就是说,如果一个类中有对象引用,那么引用的对象数据是不会克隆过来的。
结构图
浅复制
private WorkExperience work;//引用工作经历对象
public Resume(string name)
{
this.name = name;
work = new WorkExperience(); //同时实例化工作对象
}
public void SetWorkExperience(string workDate,string company)
{
work.WorkDate = workDate;
work.Company = company;
}
public void Display()
{
Console.WriteLine("{0}{1}{2}",name,sex,age);
Console.WriteLine("工作经历:{0}{1}",work.WorkDate,work.company);
}
public Object Clone()
{
return (Object)this.MemberwiseClone();//创建当前的浅表副本
}
class WorkExperience
{
private string workDate;
public string WorkDate
{
get { return workDate; }
set { workDate = value; }
}
public string company;
public string Company
{
get { return company; }
set { company = value; }
}
}
深复制
private Resume (WorkExperience work)
{
this.work = (WorkExperience)work.Clone();//提供clone方法的私有构造函数,以便克隆工作经历类的数据
}
public Object Clone()
{
Resume obj = new Resume(this.work); //调用私有的构造方法,让工作经历类克隆完成,然后再给这个简历类对象相关赋值
obj.name = this.name;
obj.sex = this.sex;
obj.age = this.age;
return obj; //返回一个深复制的对象
}
class WorkExperience:ICloneable //让工作经历类实现ICloneable接口
{
private string workDate;
public string WorkDate
{
get { return workDate; }
set { workDate = value; }
}
public string company;
public string Company
{
get { return company; }
set { company = value; }
}
public Object Clone() //工作经历类实现克隆方法
{
return (Object)this.MemberwiseClone();
}
}
六、建造者模式
定义:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示
个人理解,也就是说当我们需要将一个复杂对象的构建与它的表示分离,使得同样的构建可以创建不同的表示意图是,则可用建造者模式。它是逐步建造产品的,所以建造者的Builder类(建造小人的各个部分的抽象类)里的那些建造方法必须要足够普遍,以便为各种类型的具体建造者构造。
优点及用法:其可使得建造代码与表示代码分离,由于建造者隐藏了该产品是如何组装的,所以若需要改变一个产品的内部表示,只需要再定义一个具体的建造者就可以了。其主要用于创建一些复杂的对象,这些对象内部构建间的建造顺序通常是稳定的,但对象内部的构建通常面临着复杂的变化。
结构图:
product 产品类,多个部件组成
class Product
{
IList<string> parts = new List<string>();
public void Add(string part)
{
parts.Add(part);
}
public void Show()
{
Console.WriteLine("1");
foreach (string part in parts)
{
Console.WriteLine(part);
}
}
}
Builder类---抽象建造者,确定部件的组成,声明一个得到产品建造后结果的方法GetResult
abstract class Builder
{
public abstract void BuildPartA();
public abstract void BuildPartB();
public abstract Product GetResult();
}
ConcreteBuilder类---具体建造者类
class ConcreteBuilder1 : Builder
{
private Product product = new Product();
public override void BuildPartA()
{
product.Add("部件A");
}
public override void BuildPartB()
{
product.Add("部件B");
}
public override Product GetResult()
{
return product;
}
}
Director类---指挥者类
class Director
{
public void Consturct(Builder builder)
{
builder.BuildPartA();
builder.BuildPartB();
}
}
客户端
Director director = new Director();
Builder b1 = new ConcreteBuilder1();
director.Consturct(b1);
Product p1 = b1.GetResult();
p1.Show();