工厂方法模式:提供一个简单的决策类,根据条件生成产品。
抽象工厂模式:提供一个创建并返回一系列产品的接口。
单件模式:某个类只能有一个实例。提供一个全局访问点。(可拓展到有限个实例)
生成器模式:将一个复杂对象的构建于呈现分开,以便根据不同需要创建不同的形式。
原型模式:先实例化一个类,然后克隆或者拷贝该类来构建新的实例。可以用共有方法进一步修改这些实例。
难点:抽象工厂模式与生成器模式的比较
简单工厂模式:专门定义一个类来负责创建其他类的实例,被创建的实例通常都具有共同的父类。它又称为静态工厂方法模式,属于类的创建型模式。
简单工厂模式的实质是由一个工厂类根据传入的参数,动态决定应该创建哪一个产品类(这些产品类继承自一个父类或接口)的实例。
意图:提供一个类,由它负责根据一定的条件创建某一具体类的实例
角色及其职责:
- 工厂(Creator)角色:简单工厂模式的核心,它负责实现创建所有实例的内部逻辑。工厂类可以被外界直接调用,创建所需的产品对象。
- 抽象(Product)角色:简单工厂模式所创建的所有对象的父类,它负责描述所有实例所共有的公共接口。
- 具体产品(Concrete Product)角色:简单工厂模式的创建目标,所有创建的对象都是充当这个角色的某个具体类的实例。一般来讲它是抽象产品类的子类,实现了抽象产品类中定义的所有接口方法。
模式的特点:
- 优点:简单工厂模式的创建目标,所有创建的对象都是充当这个角色的某个具体类的实例。在这个模式中,工厂类是整个模式的关键所在。它包含必要的判断逻辑,能够根据外界给定的信息,决定究竟应该创建哪个具体类的对象。用户在使用时可以直接根据工厂类去创建所需的实例,而无需了解这些对象是如何创建以及如何组织的。有利于整个软件体系结构的优化。
- 缺点:体现在其工厂类上,由于工厂类集中了所有实例的创建逻辑,所以"高内聚"方面做的并不好。另外,当系统中的具体产品类不断增多时,可能会出现要求工厂类也要做相应的修改,扩展性并不很好。
工厂方法模式: 工厂方法模式的对简单工厂模式进行了抽象。有一个抽象的Factory类(可以是抽象类和接口),这个类将不在负责具体的产品生产,而是只制定一些规范,具体的生产工作推延到其子类去完成。
意图:定义一个用户创建对象的接口,让子类决定实例化哪一个类,工厂方法模式使一个类的实例化延迟到其子类。
优点:实现了开闭原则,可以在不改变工厂的前提下增加新产品。
实现要点:
- Factory Method模式的两种情况:一是Creator类是一个抽象类且它不提供它所声明的工厂方法的实现;二是Creator是一个具体的类且它提供一个工厂方法的缺省实现。
- 工厂方法是可以带参数的。
- 工厂的作用并不仅仅只是创建一个对象,它还可以做对象的初始化,参数的设置等。
效果:
- 用工厂方法在一个类的内部创建对象通常比直接创建对象更灵活。
- Factory Method模式通过面向对象的手法,将所要创建的具体对象的创建工作延迟到了子类,从而提供了一种扩展的策略,较好的解决了这种紧耦合的关系。
适用性:
- 当一个类不知道它所必须创建的对象的类的时候。
- 当一个类希望由它的子类来指定它所创建的对象的时候。
- 当类将创建对象的职责委托给多个帮助子类中的某一个,并且你希望将哪一个帮助子类是代理者这一信息局部化的时候。
附注:
- 基类是一个抽象类,模式必须返回一个完整的可工作的类
- 基类包含默认方法,除非这些默认方法不能胜任才调用子类方法
- 可以讲参数传递给工厂,告诉工厂返回哪一个类。
抽象工厂模式:提供创建对象的接口。与工厂方法类似,但此处返回的一系列相关产品。实现过程同样推延到子系列类去实现。与工厂方法的区别在于他们的层次模型。工厂方法的抽象基类只有儿子,而抽象工厂模式却是有孙子,而且每个儿子的儿子们之间有相互关联依赖关系。
意图:提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。
角色及职责:
- 抽象工厂(Abstract Factory):声明生成一系列抽象产品的方法
- 具体工厂(Concrete Factory):执行生成一系列抽象产品的方法,生成一系列具体的产品
- 抽象产品(Abstract Product):为这一系列的某一种产品声明接口
- 具体产品(Product):定义具体工厂生成的具体产品的对象,实现产品接口
- 客户(Client):我们的应用程序客户端(不要理解成人),使用抽象产品和抽象工厂生成对象。
附注:简单工厂、工厂方法、抽象工厂比较
简单工厂,工厂方法,抽象工厂都属于设计模式中的创建型模式。其主要功能都是帮助我们把对象的实例化部分抽取了出来,优化了系统的架构,并且增强了系统的扩展性。
- 简单工厂:简单工厂模式的工厂类一般是使用静态方法,通过接收的参数的不同来返回不同的对象实例。不修改代码的话,是无法扩展的。
- 工厂方法:工厂方法是针对每一种产品提供一个工厂类。通过不同的工厂实例来创建不同的产品实例。在同一等级结构中,支持增加任意产品。
- 抽象工厂:抽象工厂是应对产品族概念的。比如说,每个汽车公司可能要同时生产轿车,货车,客车,那么每一个工厂都要有创建轿车,货车和客车的方法。应对产品族概念而生,增加新的产品线很容易,但是无法增加新的产品。
小结:
- 工厂模式中,重要的是工厂类,而不是产品类。产品类可以是多种形式,多层继承或者是单个类都是可以的。但要明确的,工厂模式的接口只会返回一种类型的实例,这是在设计产品类的时候需要注意的,最好是有父类或者共同实现的接口。使用工厂模式,返回的实例一定是工厂创建的,而不是从其他对象中获取的。工厂模式返回的实例可以不是新创建的,返回由工厂创建好的实例也是可以的。
区别:
- 简单工厂 : 用来生产同一等级结构中的任意产品。(对于增加新的产品,无能为力)
- 工厂模式 :用来生产同一等级结构中的固定产品。(支持增加任意产品)
- 抽象工厂:用来生产不同产品族的全部产品。(对于增加新的产品,无能为力;支持增加产品族)
以上三种工厂方法在等级结构和产品族这两个方向上的支持程度不同。所以要根据情况考虑应该使用哪种方法。
单件模式:保证某个类有且仅有一个实例。
意图:单件模式保证应用只有一个全局惟一的实例,并且提供一个访问它的全局访问点。
结构:包括防止其他对象创建实例的私有构造函数、保存惟一实例的私有变量和全局访问接口等。
效果:单件提供了全局惟一的访问入口,因此易于控制可能发生的冲突。单件是对类静态函数的一种改进,首先它避免了全局变量对系统的污染;其次正常类可以有子类,可以定义虚函数,具有多态性。而类中的静态方法是不能定义为虚函数的,因此不具有多态性。单件模式可以扩展为多件,即允许有受控的多个实例存在。
适用场合:当类只能有一个实例存在,并且可以在全局访问时。这个惟一的实例应该可以通过子类实现扩展,并且用户无须更改代码即可使用。我们前面介绍的工厂类经常被实例化为全局惟一的单件,可能的单件还有管理日志的对象、关键字生成对象和外部设备接口对象等。
实现:
- 私有构造函数防止在外部实例化。
- 保存惟一实例的静态的私有变量。
- 初始化并获得惟一实例的静态方法。
比较:
- 与全局变量的比较:单件模式维护自身的实例化,在使用时是安全的。一个全局对象无法自行维护,也就无法避免重复创建多个实例,系统资源会被大量占用。更糟糕的是在很多情况下会出现逻辑问题,当这些对象访问相同的资源(例如串口时)时,会发生访问冲突。
- 与实用类静态方法的比较:实用类提供系统公用的静态方法,并且也经常采用私有化的构造函数。与单件不同,它没有实例,其中的方法全部是静态方法。
(1)实用类不保存状态,仅提供功能。
(2)实用类不具有多态性,而单件可以有子类。
(3)单件是对象,实用类只是方法的集合。
实现:
- 简单实现
public sealed class Singleton
{
static Singleton instance=null;
Singleton()
{
}
public static Singleton Instance
{
get
{
if (instance==null)
{
instance = new Singleton();
}
return instance;
}
}
}
这种方式的实现对于线程来说并不是安全的,因为在多线程的环境下有可能得到Singleton类的多个实例。如果同时有两个线程去判断(instance == null),并且得到的结果为真,这时两个线程都会创建类Singleton的实例,这样就违背了Singleton模式的原则。实际上在上述代码中,有可能在计算出表达式的值之前,对象实例已经被创建,但是内存模型并不能保证对象实例在第二个线程创建之前被发现。
该实现方式主要有两个优点:
- 由于实例是在 Instance 属性方法内部创建的,因此类可以使用附加功能(例如,对子类进行实例化),即使它可能引入不想要的依赖性。
- 直到对象要求产生一个实例才执行实例化;这种方法称为"惰性实例化"。惰性实例化避免了在应用程序启动时实例化不必要的 singleton。
- 安全的线程
public sealed class Singleton
{
static Singleton instance=null;
static readonly object padlock = new object();
Singleton()
{
}
public static Singleton Instance
{
get
{
lock (padlock)
{
if (instance==null)
{
instance = new Singleton();
}
return instance;
}
}
}
}
这种方式的实现对于线程来说是安全的。我们首先创建了一个进程辅助对象,线程在进入时先对辅助对象加锁然后再检测对象是否被创建,这样可以确保只有一个实例被创建,因为在同一个时刻加了锁的那部分程序只有一个线程可以进入。这种情况下,对象实例由最先进入的那个线程创建,后来的线程在进入时(instence == null)为假,不会再去创建对象实例了。但是这种实现方式增加了额外的开销,损失了性能。
- 双重锁定
public sealed class Singleton
{
static Singleton instance=null;
static readonly object padlock = new object();
Singleton()
{
}
public static Singleton Instance
{
get
{
if (instance==null)
{
lock (padlock)
{
if (instance==null)
{
instance = new Singleton();
}
}
}
return instance;
}
}
}
这种实现方式对多线程来说是安全的,同时线程不是每次都加锁,只有判断对象实例没有被创建时它才加锁,有了我们上面第一部分的里面的分析,我们知道,加锁后还得再进行对象是否已被创建的判断。它解决了线程并发问题,同时避免在每个Instance 属性方法的调用中都出现独占锁定。它还允许您将实例化延迟到第一次访问对象时发生。实际上,应用程序很少需要这种类型的实现。大多数情况下我们会用静态初始化。这种方式仍然有很多缺点:无法实现延迟初始化。
- 静态初始化
public sealed class Singleton
{
static readonly Singleton instance=new Singleton();
static Singleton() //只要调用类名就调用.注意此处static前面不可以加权限修饰符。且该构造函数只在第一次引用该类成员的时候调用(参见msdn中关于金泰构造函数讲解部分)
{
}
Singleton()
{
}
public static Singleton Instance
{
get
{
return instance;
}
}
}
看到上面这段富有戏剧性的代码,我们可能会产生怀疑,这还是Singleton模式吗?在此实现中,将在第一次引用类的任何成员时创建实例。公共语言运行库负责处理变量初始化。该类标记为 sealed 以阻止发生派生,而派生可能会增加实例。此外,变量标记为 readonly,这意味着只能在静态初始化期间(此处显示的示例)或在类构造函数中分配变量。
该实现与前面的示例类似,不同之处在于它依赖公共语言运行库来初始化变量。它仍然可以用来解决 Singleton 模式试图解决的两个基本问题:全局访问和实例化控制。公共静态属性为访问实例提供了一个全局访问点。此外,由于构造函数是私有的,因此不能在类本身以外实例化 Singleton 类;因此,变量引用的是可以在系统中存在的唯一的实例。
由于 Singleton 实例被私有静态成员变量引用,因此在类首次被对 Instance 属性的调用所引用之前,不会发生实例化。
这种方法唯一的潜在缺点是,您对实例化机制的控制权较少。在 Design Patterns 形式中,您能够在实例化之前使用非默认的构造函数或执行其他任务。由于在此解决方案中由 .NET Framework 负责执行初始化,因此您没有这些选项。在大多数情况下,静态初始化是在 .NET 中实现 Singleton 的首选方法。
- 延迟初始化
public sealed class Singleton
{
Singleton()
{
}
public static Singleton Instance
{
get
{
return Nested.instance;
}
}
class Nested
{
static Nested()
{
}
internal static readonly Singleton instance = new Singleton();
}
}
这里,初始化工作有Nested类的一个静态成员来完成,这样就实现了延迟初始化,并具有很多的优势,是值得推荐的一种实现方式。
实现要点:
- Singleton模式是限制而不是改进类的创建。
- Singleton类中的实例构造器可以设置为Protected以允许子类派生。
- Singleton模式一般不要支持Icloneable接口,因为这可能导致多个对象实例,与Singleton模式的初衷违背。
- Singleton模式一般不要支持序列化,这也有可能导致多个对象实例,这也与Singleton模式的初衷违背。
- Singleton只考虑了对象创建的管理,没有考虑到销毁的管理,就支持垃圾回收的平台和对象的开销来讲,我们一般没必要对其销毁进行特殊的管理。
- 理解和扩展Singleton模式的核心是"如何控制用户使用new对一个类的构造器的任意调用"。
- 可以很简单的修改一个Singleton,使它有少数几个实例,这样做是允许的而且是有意义的。
优点 :
- 实例控制:Singleton 会阻止其他对象实例化其自己的 Singleton 对象的副本,从而确保所有对象都访问唯一实例
- 灵活性:因为类控制了实例化过程,所以类可以更加灵活修改实例化过程
缺点 :
- 开销:虽然数量很少,但如果每次对象请求引用时都要检查是否存在类的实例,将仍然需要一些开销。可以通过使用静态初始化解决此问题,上面的五种实现方式中已经说过了。
- 可能的开发混淆:使用 singleton 对象(尤其在类库中定义的对象)时,开发人员必须记住自己不能使用 new 关键字实例化对象。因为可能无法访问库源代码,因此应用程序开发人员可能会意外发现自己无法直接实例化此类。
- 对象的生存期:Singleton 不能解决删除单个对象的问题。在提供内存管理的语言中(例如基于 .NET Framework 的语言),只有 Singleton 类能够导致实例被取消分配,因为它包含对该实例的私有引用。在某些语言中(如 C++),其他类可以删除对象实例,但这样会导致 Singleton 类中出现悬浮引用。
适用性 :
- 当类只能有一个实例而且客户可以从一个众所周知的访问点访问它时。
- 当这个唯一实例应该是通过子类化可扩展的,并且客户应该无需更改代码就能使用一个扩展的实例时。
生成器模式:在软件设计中,有时候面临着一个非常复杂的对象的创建工作。这个复杂的对象通常可以分成几个较小的部分,由各个子对象组合出这个复杂对象的过程相对来说比较稳定,但是子对象的创建过程各不相同并且可能面临变化。根据OOD中的OCP原则,我们自然应该对这些子对象的创建过程进行变化封装。这就是生成器模式的思路。定义一个抽象的建造者的角色(Builder),规定所有具体的建造者都应该具有的功能——这些功能就是如何创建复杂对象的某个特定部分(子对象),而具体如何创建子对象有具体的创建者实现。再定义一个指导者的角色,它把创建者作为工具,知道如何使用这个工具来创建复杂对象。这样,客户在需要创建这个复杂对象的时候,只需要给指导者一个具体的创建者就可以了。至于具体创建者如何创建子对象的细节以及这些子对象之间的差异,不是指导者,也不是客户关心的。
意图:将一个复杂的构建与其表示相分离,使得同样的构建过程可以创建不同的表示(GoF)。
角色及指责:
- Builder:抽象生成器角色,这是一个抽象接口,定义了创建负责对象的各个组成部分的公共接口。这个接口应该足够普遍,以便具体的生成器能够通过实现这个抽象接口的不同方式和不同部分生成不同的产品。
- ConcreateBuilderA:具体生成器角色,继承并实现抽象生成器的接口,完成具体产品部件的建造。除此以外,具体生成器还需要额外提供一个检索产品的方法,以便指导者能在产品生成后获得它。这个方法通常包含的是复杂产品的装配逻辑。
- Director:指导者角色,指导具体生成器来一步一步完成产品的创建。需要特别指出的是,指导者并不需要指导具体生成器生成的产品,它只需要了解如何使用生成器制造产品,制造出的产品的细节,只有具体生成器才知道。
- Product:产品,被创建出来的复杂的产品。由ConcreteBuilder创建这个复杂产品的内部表示并定义它的装配过程。通常,Product定义了它的组成部件的类,以及将这些部件装配成最终产品的接口。一个系统中可能还有多个Product类,而且这些类之间不一定会有共同的接口,可以是完全互不相关的。
适用范围:生成器模式用于分步骤构建一个复杂的对象,如何划分步骤是一个稳定的算法,而复杂对象的各个部分的创建过程则经常变化。通过把各部件的创建过程封装到生成器中,使得复杂对象的创建过程和最终表象实现分分离。使用生成器模式,隐藏了具体产品的表示方法、内部结构和装配过程。通过定义一个新的生成器,就可以改变产品的内部表示。生成器模式中的指导者角色控制着生成器生成部件的过程。因此,通过指导者,可以实现对复杂产品生成步骤进行精细的控制——这一点在复杂产品部件的生成必须遵循一定的次序时显得十分方便。
- 当创建复杂对象的算法应该独立于该对象的组成部分以及它们的装配方式时。
- 当构造过程必须允许被构造的对象有不同的表示时。
优点:采用生成器模式可以轻松地改变产品的内部表示。生成器模式将构造代码和表示代码分开。构造过程可以更精细地控制,生成器模式强调的是产品的构造过程,产品各部分具有依赖关系非常重要。需要注意的是,不同生成器产生的对象可能不属于同一类型,因此使用生成器的客户必须知道产品的具体类型。这意味着生成器经常不能互换,不同的生成器针对的客户程序也不相同。
附注:与抽象工厂模式的比较
生成器模式关注于将构造对象的过程和构造的各个部分分开,而抽象工厂关注于构建一个产品系列。实际上,最大的区别是生成器模式创建的产品不一定有共同的父类,只要有类似的构造过程即可。实际上我们常见到的文件资源管理器的实现完全可以使用生成器模式。
生成器模式和工厂模式很相象,但是它们有很明显的区别。那就是工厂模式只是根据给的参数不同,工厂"生产"并返回不同的对象。生成器模式除了根据不同参数"生产"不同对象外,这些不同的对象还包含着不同的数据。生成器模式比工厂模式复杂就复杂在多"数据"这一部分。
注意问题:
- 装配和构造接口 :生成器逐步的构造它们的产品。因此Builder类接口必须足够普遍,以便为各种类型的具体生成器构造产品。
- 没有抽象类 :通常情况下,由具体生成器生成的产品,它们的表示相差是如此之大以至于给不同的产品以公共父类没有太大意思。 (与抽象工厂的明显区别)
- 在Builder中却省的方法为空:定义为空方法可以使客户只重定义他们所感兴趣的操作。
原型模式:
定义:用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。Prototype模式允许一个对象再创建另外一个可定制的对象,根本无需知道任何如何创建的细节,工作原理是:通过将一个原型对象传给那个要发动创建的对象,这个要发动创建的对象通过请求原型对象拷贝它们自己来实施创建。
——————————————————————————————————————
意图:用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
角色及职责:
- 客户(Client)角色:客户类提出创建对象的请求。
- 抽象原型(Prototype)角色:这是一个抽象角色,通常由一个C#接口或抽象类实现。此角色给出所有的具体原型类所需的接口。在C#中,抽象原型角色通常实现了ICloneable接口。
- 具体原型(Concrete Prototype)角色:被复制的对象。此角色需要实现抽象原型角色所要求的接口
- 原型管理器(Prototype Manager)角色:创建具体原型类的对象,并记录每一个被创建的对象
优点:引入Prototype模式后不再需要一个与具体产品等级结构平行的工厂方法类,减少了类的构造,同时客户程序可以在运行时刻建立和删除原型(自定义界面时,此点尤其重要)。
要实现深拷贝,可以通过序列化的方式。抽象类及具体类都必须标注为可序列化的[Serializable]
实现要点:
- 使用原型管理器,体现在一个系统中原型数目不固定时,可以动态的创建和销毁,如上面的举的调色板的例子。
- 实现克隆操作,在.NET中可以使用Object类的MemberwiseClone()方法来实现对象的浅表拷贝或通过序列化的方式来实现深拷贝。
- Prototype模式同样用于隔离类对象的使用者和具体类型(易变类)之间的耦合关系,它同样要求这些"易变类"拥有稳定的接口。
效果:
- 它对客户隐藏了具体的产品类,因此减少了客户知道的名字的数目。
- Prototype模式允许客户只通过注册原型实例就可以将一个具体产品类并入到系统中,客户可以在运行时刻建立和删除原型。
- 减少了子类构造,Prototype模式是克隆一个原型而不是请求工厂方法创建一个,所以它不需要一个与具体产品类平行的Creater类层次。
- Portotype模式具有给一个应用软件动态加载新功能的能力。由于Prototype的独立性较高,可以很容易动态加载新功能而不影响老系统。
- 产品类不需要非得有任何事先确定的等级结构,因为Prototype模式适用于任何的等级结构
- Prototype模式的最主要缺点就是每一个类必须配备一个克隆方法。而且这个克隆方法需要对类的功能进行通盘考虑,这对全新的类来说不是很难,但对已有的类进行改造时,不一定是件容易的事。
适用性:
- 当一个系统应该独立于它的产品创建,构成和表示时;
- 当要实例化的类是在运行时刻指定时,例如,通过动态装载;
- 为了避免创建一个与产品类层次平行的工厂类层次时;
- 当一个类的实例只能有几个不同状态组合中的一种时。建立相应数目的原型并克隆它们可能比每次用合适的状态手工实例化该类更方便一些。
附注:
Prototype模式同工厂模式,同样对客户隐藏了对象的创建工作,但是,与通过对一个类进行实例化来构造新对象不同的是,原型模式是通过拷贝一个现有对象生成新对象的,达到了"隔离类对象的使用者和具体类型(易变类)之间的耦合关系"的目的。
此处引入的知识点式浅拷贝与深拷贝的问题:
概念:
a.浅拷贝(Shallow Copy影子克隆):只复制对象的基本类型,对象类型,仍属于原来的引用。
b.深拷贝(Deep Copy 深度克隆):不紧复制对象的基本类,同时也复制原对象中的对象.完全产生新对象。
实现机制:
2.深拷贝与浅拷贝实现机制:
对于值类型:
a.浅拷贝: 通过赋值等操作直接实现,将对象中的值类型的字段拷贝到新的对象中。
b.深拷贝:通过赋值等操作直接实现,将对象中的值类型的字段拷贝到新的对象中。 和浅拷贝相同
对于引用类型:
a.浅拷贝: MemberwiseClone 方法创建一个浅副本,方法是创建一个新对象,如果字段是值类型的,则对该字段执行逐位复制。如果字段是引用类型,则复制引用原始对象,与原对象引用同一对象。
b.深拷贝:拷贝对象应用,也拷贝对象实际内容,也就是创建了一个新的改变新对象
不会影响到原始对象的内容
这种情况需要为其实现ICloneable接口中提供的Clone方法。
差别就是在对于引用类型的实现深拷贝和浅拷贝的时候的机制不同,前者是MemberwiseClone 方法实现,后者是通过继承实现ICloneable接口中提供的Clone方法,实现对象的深拷贝。