设计模式之创建型

来源:互联网 发布:mac火狐兼容性视图设置 编辑:程序博客网 时间:2024/06/06 03:56
这些设计模式提供了一种在创建对象的同时隐藏创建逻辑的方式,而不是使用new运算符直接实例化对象。这使得程序在判断针对某个给定实例需要创建哪些对象时更加灵活。

1.工厂模式(Factory Pattern)
  创建对象时不会对客户端暴露创建逻辑,而且是通过使用一个共同的接口在指向新创建的对象。
意图:定义一个创建对象的接口,让其子类自己决定实例化哪一个工厂类,工厂模式使其创建过程延迟到子类进行。
主要解决:接口选择问题。
何时使用:我们明确地计划不同条件下创建不同实例时。
如何解决:让其子类实现工厂接口,返回的也是一个抽象的产品。
关键代码:创建过程在其子类执行。
优点:1.一个调用者想创建一个对象,只要知道其名称即可。2.扩展性高,如果想增加一个产品,只要扩展一个工厂类即可。3.屏蔽产品的具体实现,调用者只关心产品的接口。
缺点:每次增加一个产品时,都需要增加一个具体类和对象实现工厂,使得系统中类的个数成倍增加,在一定程度上增加了系统的复杂度,同时也增加了系统具体类的依赖。
注意事项:作为一种创建类模式,在任何需要生成复杂对象的地方,都可以使用工厂方法模式。有一点需要注意的地方就是复杂对象适合使用工厂模式,而简单对象,特别是只需要通过 new 就可以完成创建的对象,无需使用工厂模式。如果使用工厂模式,就需要引入一个工厂类,会增加系统的复杂度。
  
2.抽象工厂模式(Abstract Factory Pattern)
  围绕一个超级工厂创建其他工厂,即工厂的工厂。
  在抽象工厂模式中,接口是负责创建一个相关对象的工厂,不需要显式指定它们的类。每个生成的工厂都能按照工厂模式提供对象。

意图:提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。(即此模式解决的是类别的选择,然后才是更加具体的类的选择)
主要解决:接口选择问题。
何时使用:系统的产品有多于一个的产品族,而系统只消费其中某一族的产品。
如何解决:在一个产品族中,定义多个产品。
关键代码:在一个工厂里聚合多个同类产品。
优点:当一个产品族中的多个对象被设计成一起工作时,它能保证客户端始终只使用同一个产品族中的对象。
缺点:产品族扩展非常困难,要增加一个系列的某一产品,既要在抽象的creator里加代码,又要在具体的里面加代码。
注意事项:产品族难扩展,产品等级易扩展。 
  
3.单例模式(Singleton Pattern)
  涉及单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
  注意:单例类只能有一个实例;单例类必须自己创建自己的唯一实例;单例类必须给所有其他对象提供这一实例。

意图:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
主要解决:一个全局使用的类频繁地创建与销毁。
何时使用:当想控制实例数量,节省系统资源时。
如何解决:判断系统是否已经有这个单例,如果有则返回,如果没有则创建。
关键代码:构造函数是私有的。
优点:1.在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例;2.避免对资源的多重占用(如写文件操作)
注意事项:getInstance()方法中需要使用同步锁synchronized(Singleton.class)防止多线程同时进入造成instance被多次实例化。

单例模式的六种实现方式:
1)懒汉式,线程不安全
是否Lazy初始化:是
是否多线程安全:否
实现难度:易
描述:最基本的实现方式,不支持多线程(因为没有加锁),严格意义上不算单例模式。在多线程下不能正常工作。
代码实例:
public class Singleton { 
    private static Singleton instance; 
    private Singleton (){} 
 
    public static Singleton getInstance() { 
    if (instance == null) { 
        instance = new Singleton(); 
    } 
    return instance; 
    } 
}
  下面的几种实现方式都支持多线程,但性能上有差异。
2)懒汉式,线程安全
是否 Lazy 初始化:
是否多线程安全:
实现难度:
描述:这种方式具备很好的 lazy loading,能够在多线程中很好的工作,但是,效率很低,99% 情况下不需要同步。
优点:第一次调用才初始化,避免内存浪费。
缺点:必须加锁 synchronized 才能保证单例,但加锁会影响效率。
getInstance() 的性能对应用程序不是很关键(该方法使用不太频繁)。
代码实例:
public class Singleton { 
    private static Singleton instance; 
    private Singleton (){} 
    public static synchronized Singleton getInstance() { 
    if (instance == null) { 
        instance = new Singleton(); 
    } 
    return instance; 
    } 

3)饿汉式(推荐使用)
是否 Lazy 初始化:
是否多线程安全:
实现难度:
描述:这种方式比较常用,但容易产生垃圾对象。
优点:没有加锁,执行效率会提高。
缺点:类加载时就初始化,浪费内存。
它基于 classloder 机制避免了多线程的同步问题,不过,instance 在类装载时就实例化,虽然导致类装载的原因有很多种,在单例模式中大多数都是调用 getInstance 方法, 但是也不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化 instance 显然没有达到 lazy loading 的效果。
代码实例:

public class Singleton { 
    private static Singleton instance = new Singleton(); 
    private Singleton (){} 
    public static Singleton getInstance() { 
    return instance; 
    } 

4)双检锁/双重校验锁(DCL,即double-checked locking)
JDK 版本:JDK1.5 起
是否 Lazy 初始化:
是否多线程安全:
实现难度:较复杂
描述:这种方式采用双锁机制,安全且在多线程情况下能保持高性能。
getInstance() 的性能对应用程序很关键。
代码实例:
public class Singleton { 
    private volatile static Singleton singleton; 
    private Singleton (){} 
    public static Singleton getSingleton() { 
    if (singleton == null) { 
        synchronized (Singleton.class) { 
        if (singleton == null) { 
            singleton = new Singleton(); 
        } 
       
    } 
    return singleton; 
    } 

5)登记式/静态内部类
是否 Lazy 初始化:
是否多线程安全:
实现难度:一般
描述:这种方式能达到双检锁方式一样的功效,但实现更简单。对静态域使用延迟初始化,应使用这种方式而不是双检锁方式。这种方式只适用于静态域的情况,双检锁方式可在实例域需要延迟初始化时使用。
这种方式同样利用了 classloder 机制来保证初始化 instance 时只有一个线程,它跟第 3 种方式不同的是:第 3 种方式只要 Singleton 类被装载了,那么 instance 就会被实例化(没有达到 lazy loading 效果),而这种方式是 Singleton 类被装载了,instance 不一定被初始化。因为 SingletonHolder 类没有被主动使用,只有显示通过调用 getInstance 方法时,才会显示装载 SingletonHolder 类,从而实例化 instance。想象一下,如果实例化 instance 很消耗资源,所以想让它延迟加载,另外一方面,又不希望在 Singleton 类加载时就实例化,因为不能确保 Singleton 类还可能在其他的地方被主动使用从而被加载,那么这个时候实例化 instance 显然是不合适的。这个时候,这种方式相比第 3 种方式就显得很合理。
代码实例:

public class Singleton { 
    private static class SingletonHolder { 
    private static final Singleton INSTANCE = new Singleton(); 
    } 
    private Singleton (){} 
    public static final Singleton getInstance() { 
    return SingletonHolder.INSTANCE; 
    } 

6)枚举
JDK 版本:JDK1.5 起
是否 Lazy 初始化:
是否多线程安全:
实现难度:
描述:这种实现方式还没有被广泛采用,但这是实现单例模式的最佳方法。它更简洁,自动支持序列化机制,绝对防止多次实例化。
这种方式是 Effective Java 作者 Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还自动支持序列化机制,防止反序列化重新创建新的对象,绝对防止多次实例化。不过,由于 JDK1.5 之后才加入 enum 特性,用这种方式写不免让人感觉生疏,在实际工作中,也很少用。
不能通过 reflection attack 来调用私有构造方法。
代码实例:
public enum Singleton { 
    INSTANCE
    public void whateverMethod() { 
    } 
}
经验之谈:一般情况下,不建议使用第 1 种和第 2 种懒汉方式,建议使用第 3 种饿汉方式。只有在要明确实现 lazy loading 效果时,才会使用第 5 种登记方式。如果涉及到反序列化创建对象时,可以尝试使用第 6 种枚举方式。如果有其他特殊的需求,可以考虑使用第 4 种双检锁方式。

4.建造者模式(Builder Pattern)
  使用多个简单的对象一步一步构建成一个复杂的对象。一个Builder类会一步一步构造最终的对象,该Builder类是独立于其他对象的。
意图:将一个复杂的构建与其表示相分离,使得同样的构建过程可以创建不同的表示。
主要解决:在软件系统中,有时候面临着“一个复杂对象”的创建工作,其通常由各个部分的子对象用一定的算法构成。由于需求的变化,这个复杂对象的各个部分经常面临着剧烈的变化,但是将它们组合在一起的算法却相对稳定。
何时使用:一些基本部件不会变,而其组合经常变化的时候。
如何解决:将变与不变分离开。
关键代码:建造者:创建和提供实例;导演:管理建造起来的实例的依赖关系。
优点:建造者独立、易扩展;便于控制细节风险;
缺点:产品必须有共同点、范围有限制;如内部变化复杂,会有很多的建造类;
使用场景:1.需要生成的对象具有复杂的内部结构;2.需要生产的对象内部属性本身相互依赖。
注意事项:与工厂模式的区别是:建造者模式更加关注与零件装配的顺序。

5.原型模式(Prototype Pattern)
  用于创建重复的对象,同时又能保证性能。
  这种模式是实现了一个原型接口,该接口用户创建当前对象的克隆。当直接创建对象的代价比较大时,则采用这种模式。(主要是通过实现Cloneable接口,重写clone方法来实现的)

意图:用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
主要解决:在运行期建立和删除原型。
何时使用:1.当一个系统应该独立于它的产品创建,构成和表示时。2.当要实例化的类是在运行时刻指定时,例如,通过动态装载;3.为了避免创建一个与产品类层次平行的工厂类层次时。4.当一个类的实例只能有几个不同状态组合中的一种时。建立相应数目的原型并克隆它们可能比每次用合适的状态手工实例化该类更方便一些。
如何解决:利用已有的一个原型对象,快速地生成和原型对象一样的实例。
关键代码:1.实现克隆操作;在Java继承Cloneable,重写clone(),在.NET中可以使用Object类的MemberwiseClone()方法来实现对象的浅拷贝或通过序列化的方式来实现深拷贝。2.原型模式同样用于隔离类对象的使用者和具体类型(易变类)之间的耦合关系,它同样要求这些’易变类‘用于稳定的接口。
应用实例:1、细胞分裂。2、JAVA 中的 Object clone() 方法。
优点:1、性能提高。2、逃避构造函数的约束。
缺点:1、配备克隆方法需要对类的功能进行通盘考虑,这对于全新的类不是很难,但对于已有的类不一定很容易,特别当一个类引用不支持串行化的间接对象,或者引用含有循环结构的时候。2、必须实现 Cloneable 接口。3、逃避构造函数的约束。
使用场景:1、资源优化场景。2、类初始化需要消化非常多的资源,这个资源包括数据、硬件资源等。3、性能和安全要求的场景。4、通过 new 产生一个对象需要非常繁琐的数据准备或访问权限,则可以使用原型模式。5、一个对象多个修改者的场景。6、一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时,可以考虑使用原型模式拷贝多个对象供调用者使用。7、在实际项目中,原型模式很少单独出现,一般是和工厂方法模式一起出现,通过 clone 的方法创建一个对象,然后由工厂方法提供给调用者。原型模式已经与 Java 融为浑然一体,大家可以随手拿来使用。
注意事项:与通过对一个类进行实例化来构造新对象不同的是,原型模式是通过拷贝一个现有对象生成新对象的。浅拷贝实现 Cloneable,重写,深拷贝是通过实现 Serializable 读取二进制流。
原创粉丝点击