创建型模式上

1、创建型模式概述
 
      创建型模式主要的关注点是“怎样创建对象?”,它的主要特点是“将对象的创建和使用分离”。这样可以降低系统的耦合度。使用者不需要关心对象的创建细节。对象的创建由相关的工厂来完成。就像去生产购买东西,我们并不关心商品怎么生产出来,因为他们有专门的厂商生产。
 
根据对象的创建和组合方式不同,创建型模式分为以下几种:
(1)单例模式:类只能生成一个实例,该类提供一个全局访问点供外部获取该实例,其拓展是有限多例模式。
(2)原型模式:将一个对象作为原型,通过对其进行复制而克隆出多个和原型类似的新实例。
(3)工厂方法模式:定义一个用于创建产品的接口,由子类决定生产什么产品
(4)抽象工厂模式:提供一个创建产品族的接口,其每个子类可以生产一系列相关的产品。
(5)建造者模式:将一个复杂的对象分解成多个相对简单的部分,然后根据不同需要分别创建它们,最后构建成该复杂对象
上述五种创建型模式,除了工厂方法模式属于类创建型模式,其他的全部属于对象创建型模式。
 
2、单例模式
 
类的构造函数是私有,外部类无法调用该构造函数,也就无法生成多个实例,该类自身必须定义一个静态私有实例,并向外提供一个静态的公有函数用于创建或者获取该静态私有
实例;
 
单例模式的主要角色如下。
  • 单例类:包含一个实例且能自行创建这个实例的类。
  • 访问类:使用单例的类。
 
类结构图如下:
 
单例模式通常有两种实现方式:
 
饿汉单例,类一旦加载就创建一个单例,保证在调用getInstance方法之前单例已经存在了。
public class HungrySingleton{
    private static final HungrySingleton instance = new HungrySingleton();
    private HungrySingleton(){}
    public static HungrySingleton getInstance(){
      return instance;
    }
}
饿汉式单例在类创建的同时就已经创建好一个静态的对象供系统使用,以后不再改变,所以是线程安全的,可以直接用于多线程而不会出现问题。 单例会在加载类后一开始就被初始化,即使客户端没有调用 getInstance()方法。饿汉式的创建方式在一些场景中将无法使用:譬如 Singleton 实例的创建是依赖参数或者配置文件的,在 getInstance() 之前必须调用某个方法设置参数给它,那样这种单例写法就无法使用了。
 
懒汉式单例,类加载时候没有生成实例,只有第一次调用geInstance方法时才去创建这个单例。
 
public class LazySingleton
{
    private static volatile LazySingleton instance=null;    //保证 instance 在所有线程中同步
    private LazySingleton(){}    //private 避免类在外部被实例化
    public static synchronized LazySingleton getInstance()
    {
        //getInstance 方法前加同步
        if(instance==null)
        {
            instance=new LazySingleton();
        }
        return instance;
    }
}
但是如果编写的是多线程程序,则不能删除上例代码中的关键字 volatile 和 synchronized,否则将存在线程非安全的问题。如果不删除这两个关键字就能保证线程安全,但是每次访问时都要同步,会影响性能,且消耗更多的资源,这是懒汉式单例的缺点。 注意区别下面的双重检查锁模式。
 
除了上述的懒汉模式和饿汉模式,其实单例的实现模式还有另外几种,分别是:
双重检查锁(实际上也称作双重锁懒汉模式): 是一种使用同步块加锁的方法,因为会有两次检查 instance == null,一次是在同步块外,一次是在同步块内。为什么在同步块内还要再检验一次?因为可能会有多个线程一起进入同步块外的 if,如果在同步块内不进行二次检验的话就会生成多个实例了。
在懒汉模式中,getInstance方法的前面加上关键字synchronized, 会导致很大的性能开销,并且加锁其实只需要在第一次初始化的时候用到,之后的调用都没必要再进行加锁在双重检查锁中,代码会检查两次单例类是否有已存在的实例,一次加锁一次不加锁,一次确保不会有多个实例被创建。
public class DoubleCheckSingleton{
private  static volatile  DoubleCheckSingleton instance;//静止指令重排,就是对于一个 volatile 变量的写操作都先行发生于后面对这个变量的读操作
private DoubleCheckSingleton(){}
   public static DoubleCheckSingleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new DoubleCheckSingleton();
                }
            }
        }
        return instance;
    }
}
这样写有好处: 检查变量是否被初始化(不去获得锁),如果已被初始化则立即返回, 如果没有获取锁,再次检查变量是否已经被初始化,如果还没被初始化就初始化一个对象。如果多个线程同时了通过了第一次检查,并且其中一个线程首先通过了第二次检查并实例化了对象,那么剩余通过了第一次检查的线程就不会再去实例化对象。 除了初始化的时候会出现加锁的情况,后续的所有调用都会避免加锁而直接返回,解决了性能消耗的问题,解决了上述的懒汉单例的缺点。
静态内部类:
public class Singleton{
  private Singleton(){}
  private static class SingletonHoler{
     private static Singleton INSTANCE = new Singleton();
}
  public static Singleton getInstance(){
    return SingletonHoler.INSTANCE;
  }
}
静态内部类的优点是:外部类加载时并不需要立即加载内部类,内部类不被加载则不去初始化INSTANCE,故而不占内存。即当SingleTon第一次被加载时,并不需要去加载SingleTonHoler,只有当getInstance()方法第一次被调用时,才会去初始化INSTANCE,第一次调用getInstance()方法会导致虚拟机加载SingleTonHoler类,这种方法不仅能确保线程安全,也能保证单例的唯一性,同时也延迟了单例的实例化。
 
那么,静态内部类又是如何实现线程安全的呢?
首先,我们先了解下类的加载时机。
类加载时机:JAVA虚拟机在有且仅有的5种场景下会对类进行初始化。
1.遇到new、getstatic、setstatic或者invokestatic这4个字节码指令时,对应的java代码场景为:new一个关键字或者一个实例化对象时、读取或设置一个静态字段时(final修饰、已在编译期把结果放入常量池的除外)、调用一个类的静态方法时。
2.使用java.lang.reflect包的方法对类进行反射调用的时候,如果类没进行初始化,需要先调用其初始化方法进行初始化。
3.当初始化一个类时,如果其父类还未进行初始化,会先触发其父类的初始化。
4.当虚拟机启动时,用户需要指定一个要执行的主类(包含main()方法的类),虚拟机会先初始化这个类。
5.当使用JDK 1.7等动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且这个方法句柄所对应的类没有进行过初始化,则需要先触发其初始化。
这5种情况被称为是类的主动引用,注意,这里《虚拟机规范》中使用的限定词是"有且仅有",那么,除此之外的所有引用类都不会对类进行初始化,称为被动引用。静态内部类就属于被动引用的行列。
 
再看下getInstance()方法,调用的是SingleTonHoler.INSTANCE,取的是SingleTonHoler里的INSTANCE对象,跟上面那个DCL方法不同的是,getInstance()方法并没有多次去new对象,故不管多少个线程去调用getInstance()方法,取的都是同一个INSTANCE对象,而不用去重新创建。当getInstance()方法被调用时,SingleTonHoler才在SingleTon的运行时常量池里,把符号引用替换为直接引用,这时静态对象INSTANCE也真正被创建,然后再被getInstance()方法返回出去,这点同饿汉模式。那么INSTANCE在创建过程中又是如何保证线程安全的呢?在《深入理解JAVA虚拟机》中,有这么一句话:
 虚拟机会保证一个类的<clinit>()方法在多线程环境中被正确地加锁、同步,如果多个线程同时去初始化一个类,那么只会有一个线程去执行这个类的<clinit>()方法,其他线程都需要阻塞等待,直到活动线程执行<clinit>()方法完毕。如果在一个类的<clinit>()方法中有耗时很长的操作,就可能造成多个进程阻塞(需要注意的是,其他线程虽然会被阻塞,但如果执行<clinit>()方法后,其他线程唤醒之后不会再次进入<clinit>()方法。同一个加载器下,一个类型只会初始化一次。),在实际应用中,这种阻塞往往是很隐蔽的。
故而,可以看出INSTANCE在创建过程中是线程安全的,所以说静态内部类形式的单例可保证线程安全,也能保证单例的唯一性,同时也延迟了单例的实例化。
那么,是不是可以说静态内部类单例就是最完美的单例模式了呢?其实不然,静态内部类也有着一个致命的缺点,就是传参的问题,由于是静态内部类的形式去创建单例的,故外部无法传递参数进去,例如Context这种参数,所以,我们创建单例时,可以在静态内部类与DCL模式里自己斟酌。
 
枚举:    
public enum Singleton{
INSTANCE;
public void method(){
//TODO
}
枚举在java中与普通类一样,都能拥有字段与方法,而且枚举实例创建是线程安全的,在任何情况下,它都是一个单例。可直接以 SingleTon.INSTANCE的方式调用。
 
单例模式适用于以下的场景和特点:
 
(1)在应用场景中,某类只要求生成一个对象的时候,如每个人的身份证号
(2)当对象需要被共享的场合,数据库连接池、web的配置对象
(3)某类需要频繁实例化,而创建的对象又频繁被销毁的时候,如多线程的线程池,网络连接池
 
单例模式模式的扩展:
单例模式可扩展为有限的多例模式,这种模式可生成为有限个实例并保存在ArrayList中,客户需要时可以随机获取。
 
 
3、原型模式
 
在有些系统中,存在大量相同对象的创建问题,如果用传统的构造函数来创建对象,会比较复杂且耗时耗资源,用原型模式生成对象就很高效。
 
3.1模式的定义与特点:
原型模式的定义:用一个已经定义的实例作为原型,通过复制该原型对象来传建一个和原型相同或者相近的新对象。在这里,原型实例指定了要创建的对象的种类。用这种方式创建的对象就非常高效。
 
3.2模式的结构与实现
 
Java提供了对象的clone()方法,用Java实现原型模式就很简单。原型模式包含以下主要角色:
  1. 抽象原型类:规定了具体原型对象必须实现的接口
  2. 具体原型类:实现抽象原型类的clone方法,它是可被复制的对象
  3. 访问类:使用具体原型类中的clone()方法来复制新的对象
原型模式的克隆分为深克隆和浅克隆,Java中的Object类提供了浅克隆的clone()方法,具体原类型只要实现Cloneable接口就可实现对象的浅克隆,这里的Cloneable接口就是抽象原型类。
实现:
//具体原型类
class Realizetype implements Cloneable
{
    Realizetype()
    {
        System.out.println("具体原型创建成功!");
    }
    public Object clone() throws CloneNotSupportedException
    {
        System.out.println("具体原型复制成功!");
        return (Realizetype)super.clone();
    }
}
//原型模式的测试类
public class PrototypeTest
{
    public static void main(String[] args)throws CloneNotSupportedException
    {
        Realizetype obj1=new Realizetype();
        Realizetype obj2=(Realizetype)obj1.clone();
        System.out.println("obj1==obj2?"+(obj1==obj2));
    }
}
3.3原型模式应用实例:
 
同一学校的“三好学生”奖状除了获奖人姓名不同,其他都相同,属于相似对象的复制,同样可以用原型模式创建,然后再做简单修改就可以了。图所示是三好学生奖状生成器的结构图。
实现代码:
//奖状类
class citation implements Cloneable
{
    String name;
    String info;
    public void setCollege(String college) {
        this.college = college;
    }
    String college;
    citation(String name,String info,String college)
    {
        this.name=name;
        this.info=info;
        this.college=college;
        System.out.println("奖状对象被创建成功!");
    }
    void setName(String name)
    {
        this.name=name;
    }
    String getName()
    {
        return(this.name);
    }
    void display()
    {
        System.out.println(name+info+college);
    }
    public Object clone() throws CloneNotSupportedException
    {
        System.out.println("奖状拷贝成功!");
        return (citation)super.clone();
    }
}
public class ProtoTypePattern {
    public static void main(String[] args) throws Exception{
        citation obj1=new citation("张三","同学:,被评为三好学生。","计算机学院");
        obj1.display();
        citation obj2=(citation) obj1.clone();
        obj2.setName("李四");
        obj2.setCollege("经管学院");
        obj2.display();
    }
}
3.4原型模式使用场景:
 
1、对象之间相识或者相同,即只有个别的几个属性不同的时候
2、对象的创建过程比较麻烦,但是复制比较简单的时候。
 
3.5 模式的扩展 
 
原型模式可扩展为带原型的管理器的原型模式,他在原型基础上在增加一个原型管理器,ProtypeManager类,该类使用Hashmap保存多个复制的原型,Client类可以通过管理器的get(String  id)方法从中获取复制的原型。
 
应用实例:
用带原型管理器的原型模式来生成包含“圆”和“正方形”等图形的原型,并计算其面积。分析:本实例中由于存在不同的图形类,例如,“圆”和“正方形”,它们计算面积的方法不一样,所以需要用一个原型管理器来管理它们,结构图如下:
实现代码:
//接口图形
interface Shape extends Cloneable
{
    public Object clone();    //拷贝
    public void countArea();    //计算面积
}
//实现类1 正方形
class Square implements Shape  {
    @Override
    public Object clone() {
        Square square  = null;
        try{
            square = (Square)super.clone();
        }catch (Exception e) {
            System.out.println("拷贝正方形失败");
        }
        return square;
    }
    @Override
    public void countArea() {
        int a = 0;
        System.out.print("这是一个正方形,请输入它的边长:");
        Scanner input=new Scanner(System.in);
        a=input.nextInt();
        System.out.println("该正方形的面积="+a*a);
    }
}
//实现类1 圆形
class Circle implements Shape {
    @Override
    public Object clone() {
        Circle circle  = null;
        try{
            circle = (Circle)super.clone();
        }catch (Exception e) {
            System.out.println("拷贝圆失败");
        }
        return circle;
    }
    @Override
    public void countArea() {
        int a = 0;
        System.out.print("这是一个圆,请输入它的半径:");
        Scanner input=new Scanner(System.in);
        a=input.nextInt();
        System.out.println("该正方形的面积="+a*a*3.14159);
    }
}
class ProtoTypeManager {
    private HashMap<String, Shape> hashMap = new HashMap<>();
    public ProtoTypeManager()
    {
        hashMap.put("Circle",new Circle());
        hashMap.put("Square",new Square());
    }
    public void addshape(String key,Shape obj)
    {
        hashMap.put(key,obj);
    }
    public Shape getShape(String key)
    {
        Shape temp=hashMap.get(key);
        return (Shape) temp.clone();
    }
}
public class ProtoTypeShape
{
    public static void main(String[] args)
    {
        ProtoTypeManager pm=new ProtoTypeManager();
        Shape obj1=(Circle)pm.getShape("Circle");
        obj1.countArea();
        Shape obj2=(Shape)pm.getShape("Square");
        obj2.countArea();
    }
}

 

posted @ 2020-02-14 00:21  jrliu  阅读(108)  评论(0编辑  收藏  举报