天天看点

创建型模式之单例模式与工厂模式(一)

  创建型模式,就是创建对象的模式,抽象了实例化的过程。它帮助一个系统独立于如何创建、组合和表示它的那些对象。关注的时对象的创建,创建型模式将创建对象的过程进行了抽象,也可以理解为将创建对象的过程进行了封装,作为客户程序仅仅需要去使用对象,而不再关心创建过程中的逻辑。

  具体的创建型模式可分为:

单例模式(Singleton)

简单工厂模式(Simple Factory)

工厂方法模式(Factory Method)

抽象工厂模式(Abstract Factory)

原型模式(Prototype)

建造者模式(Builder)

  所谓的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法。例如:Hibernate的SessionFactory,它充当数据存储源的代理,并负责创建Session对象。SessionFactory并不是轻量级的,一般情况下,一个项目通常只需要一个SessionFactory就够,这时就需要使用单例模式。

  单例设计模式有八种方式(推荐使用1、2、6、7、8):

饿汉式(静态常量)

饿汉式(静态代码块)

懒汉式(线程不安全)

懒汉式(线程安全,同步方法)

懒汉式(线程不安全,同步代码块)

双重检查

静态内部类

枚举

  优点:这种写法比较简单,就是在类装载的时候就完成实例化,避免了线程同步问题。

  缺点:在类装载的时候就完成实例化,没有达到Lazy Loading的效果。如果从始至终从未使用过这个实例,则会造成内存的浪费。

  结论:这种单例模式可用,但是可能造成内存浪费

  优缺点:这种方式和上面的方式其实类似,只不过将类实例化的过程放在了静态代码块中,也是在类装载的时候,就执行静态代码块中的代码,初始化类的实例。优缺点和上面是一样的。

  结论:这种单例模式可用,但是可能造成内存浪费。

起到了LazingLoading的效果(使用时才创建),但是只能在单线程下使用。

如果在多线程下,多个线程同时判断singleton为null,会创建多个实例。

结论:在实际开发中,不要使用这种方式创建

采用同步方法关键字synchronized解决线程安全问题

效率太低了,每个线程在想获得类的实例时候,执行getInstance()方法都要进行同步。而其实这个方法只执行一次实例化代码就够了,后面的想获得该类实例,直接return就行了。

结论:实际开发中,不推荐使用这种方式

这种方式本意是想对6方法进行改进,因为前面同步方法效率太低了,改为同步产生实例化的代码块

但是这种同步并不能起到线程同步的作用(解决不了线程安全的问题)。因为同样如果多个线程进行判断singleton为null时,虽然会在synchronized方法外被阻塞,但是阻塞完成之后还是会继续执行产生多个不同实例对象

结论:在实际开发中,不能使用这种方式

第一个if判断能提升创建效率,如果去掉,多线程会阻塞;

第二个if判断能解决线程安全问题,如果去掉会有多个线程进入synchronized代码块中创建;

synchronized方法能保顺序执行,如果去掉也会创建多个实例;

volatile关键字能保证可见性和禁止指令重排,详细点击Volatile的应用DCL单例模式(四)

结论:实现了线程安全;延迟加载;效率较高。推荐使用

当外部类Singleton进行类转载时,静态内部是不会被装载的

当调用Singleton的getInstance()方法,用到INSTANCE静态常量时,静态类才会被装载,且只会装载一次。在装载时,线程是安全的

这种方式采用了类装载的机制来保证初始化实例时只会有个一个线程

静态内部类方式在Singleton类被装载时并不会立即实例化,而是需要实例化时,调用getInstance()方式,才会装载类StaticClass类,从而完成Singleton的实例化

类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM帮助我们保证了线程的安全性,在类进行初始化时,别的线程是无法进入的

优点:避免了线程不安全,利用静态内部类特点实现延迟加载,效率高

结论:推荐使用

这是借助JDK1.5中添加的枚举来实现单例模式。不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象

  Runtime源码

  

创建型模式之单例模式与工厂模式(一)

单例模式保证了系统内存中改类只存在一个对象,节省了系统资源,对于一些需要频繁创建销毁的对象,使用单例模式可以提高系统性能

当想实例化一个单例类的时候,必须要记住使用相应的获取对象的方法,而不是使用new

单例模式使用的场景:需要频繁创建和销毁的对象、创建对象时耗时过多或耗费资源过多(即:重量级对象),但又经常用到的对象、工具类对象、频繁访问数据库或文件的对象(比如数据源、session工厂等)

需求:

1) 披萨的种类很多(比如 GreekPizza、CheesePizza 等)

2) 披萨的制作有 prepare,bake, cut, box

3) 完成披萨店订购功能。

源码:详情

创建型模式之单例模式与工厂模式(一)

优点是比较好理解,简单易操作

缺点是违反了ocp原则,对扩展开发对修改关闭。当需要添加新的pizza种类时需要修改调用方OrderPizza(如果有多个调用方OrderPizza1,OrderPizza2...)。

  把创建Pizza对象封装到一个类(工厂类)中,在订购时只需要根据不同的orderType就能获得对应的Pizza类。

创建型模式之单例模式与工厂模式(一)

  如果需要重新添加一个pizza的种类,需要在添加一个类继承Pizza类(可选择重写方法),在简单工厂中添加对应的创建逻辑。不需要在每个OrderPizza类中修改代码。

  需求变动:客户在点披萨时,可以点不同口味的披萨,比如 北京的奶酪 pizza、北京的胡椒 pizza 或者是伦敦的奶酪 pizza、伦敦的胡椒 pizza。

   

创建型模式之单例模式与工厂模式(一)

如果这种情况还是使用简单工厂模式,创建不同的工厂类,比如BJPizzaSimpleFactory、LDPizzaSimpleFactory等,从目前的需求分析是可以的,但是会导致有很多的工厂类,考虑到软件的可维护性、可扩展性,所以选择工厂方法模式

工厂方法模式:定义了一个创建对象的抽象方法,由子类决定要实例化的类。工厂方法模式将对象的实例化推迟到子类。

  抽象工厂模式:定义了一个interface用于创建相关或有依赖关系的对象簇,而无需指明具体的类。抽象工厂模式可以将简单工厂模式和工厂方法模式进行整合。

  从设计层面看,抽象工厂模式就是对简单工厂模式的改进(或者称为进一步的抽象)。

  将工厂抽象成两层,AbsFactory(抽象工厂)和具体实现的工厂子类。程序员可以根据创建对象类型使用对应的工厂子类。这样将单个的简单工厂类变成了工厂簇,更利于代码的维护和扩展。

  如果产品的种类很多的话,使用抽象工厂模式的灵活性会很高;如果产品的种类不多的话,使用简单工厂模式就足够了

  Pizza、BJChessPizza、BJPepperPizza、LDChessPizza、LDPepperPizza与工厂方法基本相同。

创建型模式之单例模式与工厂模式(一)
创建型模式之单例模式与工厂模式(一)