原创

JAVA设计模式(篇二)

温馨提示:
本文最后更新于 2022年10月27日,已超过 919 天没有更新。若文章内的图片失效(无法正常加载),请留言反馈或直接联系我

一 单例模式

1. 单例模式的定义与特点

定义:指一个类只有一个实例,且该类能自行创建这个实例的一种模式

特点 :

  1. 单例类只有一个实例对象
  2. 该单例对象必须由单例类自行创建
  3. 单例类对外提供一个访问该单例的全局访问点

2. 单例模式的结构与实现

1. 单例模式的主要角色:

  • 单例类:包含一个实例且能自行创建这个实例的类。
  • 访问类: 使用单例的类

2. 单例模式的实现

  1. 懒汉式单例:该模式的特点是类加载时没有生成单例,只有当第一次调用getInstance方法时才去创建这个单例

    //懒汉式单例
    public class LazySingleton {
    
        //私有化构造方法
        private LazySingleton() {
        }
    
        //添加volatile关键字,防止多线程访问下出现线程不安全问题。 
        private static volatile LazySingleton lazySingleton = null;
    
        //对外暴露单例获取方法   synchronized 防止多线程访问情况下出现多个实例
        public synchronized static LazySingleton getInstance() {
            if (null == lazySingleton) {
                lazySingleton = new LazySingleton();
            }
            return lazySingleton;
        }
    
    }
    

    tips:加上 volatile 和synchronized 能保证线程安全,但是每次访问时都需要同步,会影响性能。单线程下可以不加,节省系统开销

  2. 饿汉式单例:该模式的特点是类一旦加载就创建一个单例,保证在调用getlnstance方法之前实例已经存在了。

    //饿汉式单例
    public class HungrySingleton {
    
        //私有化构造方法
        private HungrySingleton() {
        }
    
        //创建该实例
        private static HungrySingleton singleton = new HungrySingleton();
    
        //提供实例访问方法
        public static HungrySingleton getInstance() {
            return singleton;
        }
    
    }
    

    tips: 饿汉式单例在类创建的同时就已经创建好一个静态的对象供系统使用,以后不再改变,所以是线程安全的,可以直接用于多线程而不会出现问题。如果没有使用到该实例 ,则会造成空间浪费

  3. 双重校验单例模式:懒汉式单例的升级写法,将synchronized关键字写在代码块中,减小加锁的对象范围

    //双重验证单例
    public class DoubleCheckSingleton {
    
        private DoubleCheckSingleton(){}
    
        public static volatile DoubleCheckSingleton checkSingleton = null;
    
        public static DoubleCheckSingleton getInstance(){
               if (null==checkSingleton){
                   synchronized (DoubleCheckSingleton.class){
                       if (null==checkSingleton){
                           checkSingleton = new DoubleCheckSingleton();
                       }
                   }
               }
    
            return checkSingleton;
        }
    
    }
    
  4. 枚举类单例模式:以上单例模式,都可使用反射进行破坏,枚举类型是线程安全的,并且只会装载一次,使用该方式实现单例,则不会被反射破坏,因此是最安全的单例实现模式(来源:effective java)

    //枚举类单例模式
    public class EnumSingleton {
        //私有化构造方法
        private EnumSingleton(){}
        //对外提供访问方法
        public static EnumSingleton getInstance(){
            return Singleton.INSTANCE.getInstance();
        }
        //枚举类单例
        enum Singleton{
            INSTANCE;
            private EnumSingleton singleton = null;
            private EnumSingleton getInstance() {
                if (null==singleton){
                    singleton = new EnumSingleton();
                }
                return singleton;
            }
        }
    }
    

3. 单例模式的应用场景

  • 在应用场景中,某类只要求生成一个对象的时候,如一个班中的班长、每个人的身份证号等。
  • 当对象需要被共享的场合。由于单例模式只允许创建一个对象,共享该对象可以节省内存,并加快对象访问速度。如 Web中的配置对象、数据库的连接池等。
  • 当某类需要频繁实例化,而创建的对象又频繁被销毁的时候,如多线程的线程池、网络连接池等。

二 原型模式

有些系统中,存在大量相同或相似对象的创建问题,如果用穿透的构造函数来创建对象 ,会比较复杂且耗费时间和资源,此时用原型模式生成对象就很高效。

1. 原型模式的定义

用一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型相同或相似的新对象。在这里,原型实例指定了要创建的对象的种类。用这种方式创建对象非常高效,根本无须知道对象创建的细节。

2. 原型模式的结构和实现

java中提供了对象的clone()方法,所以用java实现原型模式比较简单

1. 原型模式结构

  • 抽象原型类:规定了具体原型对象必须实现的接口
  • 具体原型类: 实现抽象原型类的clone()方法,它是可被复制的对象。
  • 访问类: 使用具体原型类中的clone()方法来复制新的对象。

2. 原型模式的实现

原型模式的克隆分为浅克隆深克隆,Java 中的Object类提供了浅克隆的clone()方法,具体原型类只要实现 Cloneable 接口就可实现对象的浅克隆,这里的Cloneable接口就是抽象原型类。

//原型模式
public class Prototype implements Cloneable{

    public void sayHi(){
        System.out.println(this.hashCode());
    }

    public Prototype clone() throws CloneNotSupportedException {
        return (Prototype) super.clone();
    }

}
  • 浅克隆: 基本数据类型的克隆,直接将原数据复制一份。对象类型的克隆,复制的是该对象的引用,因此复制的对象在改动时,会影响到原本的对象。
//Cat类
public class Cat{
    private String name;
}
//User类
public class User implements Cloneable{

    private String userName;
    private Cat cat;

    public User (String userName,Cat cat){
        this.userName = userName;
        this.cat = cat;
    }

    public User clone() throws CloneNotSupportedException {
        return (User) super.clone();
    }
    public void test(){
        System.out.println(this.userName+"的猫是"+this.cat.getName());
    }

}
//测试
@Test
void test() throws CloneNotSupportedException {
    Cat cat = new Cat();
    cat.setName("布偶猫");
    User user = new User("张三",cat);
    user.test();
    User clone = user.clone();
    clone.setUserName("李四");
    clone.getCat().setName("狸花猫");
    clone.test();
    user.test();
}
//输出结果
张三的猫是布偶猫
李四的猫是狸花猫
张三的猫是狸花猫

tips:为什么String 类型不是基础数据类型,克隆后修改值却不影响原对象?

​ String是不可变对象,当String对象生成时,会开辟一个地址空间,并返回当前String对象的引用。当String对象出现修改时,会重新开辟一个地址空间,并返回修改后的对象引用。因此,当我们的克隆对象修改String值时,只会修改到克隆对象的String引用,不会影响到原本对象

  • 深克隆:避免出现浅克隆的问题

    • 实现1:将所有引用对象都实现Cloneable接口。在克隆的时候同时克隆

      //Cat类修改
      public class Cat implements Cloneable{
          private String name;
          public Cat clone() throws CloneNotSupportedException {
              return (Cat) super.clone();
          }
      }
      //User类修改
      public class User implements Cloneable{
      
          .....
          //将cat也进行克隆
          public User clone() throws CloneNotSupportedException {
              User user = (User) super.clone();
              user.cat = cat.clone();
              return user;
          }
          .....
      
      }
      //上面的测试代码执行结果
      张三的猫是布偶猫
      李四的猫是狸花猫
      张三的猫是布偶猫
      
    • 实现2:使用JSON(fast.json)进行对象转换,相当于生成的是全新的对象 。自然不会影响到原本的对象。

      @Test
      void test2(){
          Cat cat = new Cat();
          cat.setName("布偶猫");
          User user = new User("张三",cat);
          user.test();
          User clone = JSON.parseObject(JSON.toJSONString(user),User.class);
          clone.setUserName("李四");
          clone.getCat().setName("狸花猫");
          clone.test();
          user.test();
      }
      //执行结果
      张三的猫是布偶猫
      李四的猫是狸花猫
      张三的猫是布偶猫
      

3. 原型模式的应用场景

  • 对象之间相同或相似,即只是个别的几个属性不同的时候。
  • 对象的创建过程比较麻烦,但复制比较简单的时候。
正文到此结束