Java常用设计模式 单例模式/原型模式

Java常用设计模式

单例模式

在有些系统中,为了节省内存资源、保证数据内容的一致性,对某些类要求只能创建一个实例,这就是所谓的单例模式。

单例模式的定义与特点

单例(Singleton)模式的定义:指一个类只有一个实例,且该类能自行创建这个实例的一种模式。例如,Windows 中只能打开一个任务管理器,这样可以避免因打开多个任务管理器窗口而造成内存资源的浪费,或出现各个窗口显示内容的不一致等错误。

在计算机系统中,还有 Windows 的回收站、操作系统中的文件系统、多线程中的线程池、显卡的驱动程序对象、打印机的后台处理服务、应用程序的日志对象、数据库的连接池、网站的计数器、Web 应用的配置对象、应用程序中的对话框、系统中的缓存等常常被设计成单例。

单例模式在现实生活中的应用也非常广泛,例如公司 CEO、部门经理等都属于单例模型。J2EE 标准中的ServletContext 和 ServletContextConfig、Spring 框架应用中的 ApplicationContext、数据库中的连接池等也都是单例模式。

单例模式有 3 个特点:

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

单例模式的优点和缺点

单例模式的优点:

  • 单例模式可以保证内存里只有一个实例,减少了内存的开销。
  • 可以避免对资源的多重占用。
  • 单例模式设置全局访问点,可以优化和共享资源的访问。

单例模式的缺点:

  • 单例模式一般没有接口,扩展困难。如果要扩展,则除了修改原来的代码,没有第二种途径,违背开闭原则。
  • 在并发测试中,单例模式不利于代码调试。在调试过程中,如果单例中的代码没有执行完,也不能模拟生成一个新的对象。
  • 单例模式的功能代码通常写在一个类中,如果功能设计不合理,则很容易违背单一职责原则。

单例模式看起来非常简单,实现起来也非常简单。单例模式在面试中是一个高频面试题。希望大家能够认真学习,掌握单例模式,提升核心竞争力,给面试加分,顺利拿到 Offer。

单例模式的应用场景

对于 Java 来说,单例模式可以保证在一个 JVM 中只存在单一实例。单例模式的应用场景主要有以下几个方面。

  • 需要频繁创建的一些类,使用单例可以降低系统的内存压力,减少 GC。
  • 某类只要求生成一个对象的时候,如一个班中的班长、每个人的身份证号等。
  • 某些类创建实例时占用资源较多,或实例化耗时较长,且经常使用。
  • 某类需要频繁实例化,而创建的对象又频繁被销毁的时候,如多线程的线程池、网络连接池等。
  • 频繁访问数据库或文件的对象。
  • 对于一些控制硬件级别的操作,或者从系统上来讲应当是单一控制逻辑的操作,如果有多个实例,则系统会完全乱套。
  • 当对象需要被共享的场合。由于单例模式只允许创建一个对象,共享该对象可以节省内存,并加快对象访问速度。如 Web 中的配置对象、数据库的连接池等。

单例模式的结构与实现

单例模式是设计模式中最简单的模式之一。通常,普通类的构造函数是公有的,外部类可以通过“new 构造函数()”来生成多个实例。但是,如果将类的构造函数设为私有的,外部类就无法调用该构造函数,也就无法生成多个实例。这时该类自身必须定义一个静态私有实例,并向外提供一个静态的公有函数用于创建或获取该静态私有实例。

1. 单例模式的结构

单例模式的主要角色如下。

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

Singleton 模式通常有两种实现形式。

第 1 种:懒汉式单例

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

/**
 * @author Fan HaoJie
 * @Description: 懒汉单例模式
 * @date 2022/2/12 10:25
 */
@Slf4j
public class LazySingleton {
    /**
     * 懒汉单例模式
     */
    private static volatile LazySingleton singleton = null;

    /**
     * 私有实例化
     */
    private LazySingleton() {
    }

    /**
     * 当第一次调用 getInstance 方法时才去创建这个单例
     **/
    public static synchronized LazySingleton getInstance() {
        if (ObjectUtils.isEmpty(singleton)) {
            singleton = new LazySingleton();
        }
        return singleton;
    }

    public static void main(String[] args) {
        LazySingleton i1 = LazySingleton.getInstance();
        LazySingleton i2 = LazySingleton.getInstance();
        log.info("i1内存地址:{}", i1);
        log.info("i2内存地址:{}", i2);
        if (i1 == i2) {
            log.info("同一对象");
        }
    }
}

-- log output --
10:58:58.382 [main] INFO com.server.entity.LazySingleton - i1内存地址:com.server.entity.LazySingleton@5cb0d902
10:58:58.385 [main] INFO com.server.entity.LazySingleton - i2内存地址:com.server.entity.LazySingleton@5cb0d902
10:58:58.385 [main] INFO com.server.entity.LazySingleton - 同一对象

注意:如果编写的是多线程程序,则不要删除上例代码中的关键字 volatile 和 synchronized,否则将存在线程非安全的问题。如果不删除这两个关键字就能保证线程安全,但是每次访问时都要同步,会影响性能,且消耗更多的资源,这是懒汉式单例的缺点。

第 2 种:饿汉式单例

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

/**
 * @author Fan HaoJie
 * @Description: 饿汉单例模式
 * @date 2022/2/12 10:32
 */
@Slf4j
public class HungrySingleton {
    private static final HungrySingleton INSTANCE = new HungrySingleton();

    private HungrySingleton() {
    }

    public static HungrySingleton getInstance() {
        return INSTANCE;
    }

    public static void main(String[] args) {
        HungrySingleton i1 = HungrySingleton.getInstance();
        HungrySingleton i2 = HungrySingleton.getInstance();
        log.info("i1内存地址:{}", i1);
        log.info("i2内存地址:{}", i2);
        if (i1 == i2) {
            log.info("同一对象");
        }
    }
}

-- log output --
10:56:27.096 [main] INFO com.server.entity.HungrySingleton - i1内存地址:com.server.entity.HungrySingleton@4b9af9a9
10:56:27.100 [main] INFO com.server.entity.HungrySingleton - i2内存地址:com.server.entity.HungrySingleton@4b9af9a9
10:56:27.100 [main] INFO com.server.entity.HungrySingleton - 同一对象

饿汉式单例在类创建的同时就已经创建好一个静态的对象供系统使用,以后不再改变,所以是线程安全的,可以直接用于多线程而不会出现问题。

单例模式的扩展

单例模式可扩展为有限的多例(Multitcm)模式,这种模式可生成有限个实例并保存在 ArrayList 中,客户需要时可随机获取。

原型模式

在有些系统中,存在大量相同或相似对象的创建问题,如果用传统的构造函数来创建对象,会比较复杂且耗时耗资源,用原型模式生成对象就很高效,就像孙悟空拔下猴毛轻轻一吹就变出很多孙悟空一样简单。

原型模式的定义与特点

原型(Prototype)模式

的定义如下:用一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型相同或相似的新对象。在这里,原型实例指定了要创建的对象的种类。用这种方式创建对象非常高效,根本无须知道对象创建的细节。例如,Windows 操作系统的安装通常较耗时,如果复制就快了很多。在生活中复制的例子非常多,这里不一一列举了。

原型模式的优点:

  • Java 自带的原型模式基于内存二进制流的复制,在性能上比直接 new 一个对象更加优良。
  • 可以使用深克隆方式保存对象的状态,使用原型模式将对象复制一份,并将其状态保存起来,简化了创建对象的过程,以便在需要的时候使用(例如恢复到历史某一状态),可辅助实现撤销操作。

原型模式的缺点:

  • 需要为每一个类都配置一个 clone 方法
  • clone 方法位于类的内部,当对已有类进行改造的时候,需要修改代码,违背了开闭原则。
  • 当实现深克隆时,需要编写较为复杂的代码,而且当对象之间存在多重嵌套引用时,为了实现深克隆,每一层对象对应的类都必须支持深克隆,实现起来会比较麻烦。因此,深克隆、浅克隆需要运用得当。

原型模式的结构与实现

由于 Java 提供了对象的 clone() 方法,所以用 Java 实现原型模式很简单。

1. 模式的结构

原型模式包含以下主要角色。

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

2. 模式的实现

原型模式的克隆分为浅克隆和深克隆

  • 浅克隆:创建一个新对象,新对象的属性和原来对象完全相同,对于非基本类型属性,仍指向原有属性所指向的对象的内存地址。
  • 深克隆:创建一个新对象,属性中引用的其他对象也会被克隆,不再指向原有对象地址。

浅克隆:

package com.server.entity.prototype;

import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;

/**
 * @author Fan HaoJie
 * @Description: 学生模型
 * @date 2022/2/14 16:53
 */
@Slf4j
public class StudentMode implements Cloneable {

    String name;
    String description;

    public StudentMode() {
        log.info("学生原型创建成功");
    }

    public StudentMode(String name, String description) {
        this.name = name;
        this.description = description;
        log.info("学生原型创建成功");
    }

    public void setName(String name) {
        this.name = name;
    }

    public void outInfo() {
        log.info(this.name + this.description);
    }

    /**
     * 克隆方法
     */
    @SneakyThrows
    @Override
    public StudentMode clone() {
        log.info("学生原型 Clone 成功");
        return (StudentMode) super.clone();
    }

    public static void main(String[] args) {
        StudentMode studentMode = new StudentMode("胡歌同学:", "获得金鸡奖");
        StudentMode studentModeClone = studentMode.clone();
        studentModeClone.setName("周星星同学:");
        if (studentMode == studentModeClone) {
            log.info("同一对象");
        } else {
            log.info("不同对象");
        }
        studentMode.outInfo();
        studentModeClone.outInfo();
    }
}

-- log output --
17:06:44.846 [main] INFO com.server.entity.prototype.StudentMode - 学生原型创建成功
17:06:44.849 [main] INFO com.server.entity.prototype.StudentMode - 学生原型 Clone 成功
17:06:44.849 [main] INFO com.server.entity.prototype.StudentMode - 不同对象
17:06:44.849 [main] INFO com.server.entity.prototype.StudentMode - 胡歌同学:获得金鸡奖
17:06:44.849 [main] INFO com.server.entity.prototype.StudentMode - 周星星同学:获得金鸡奖

浅克隆与深克隆对比:

package com.server.entity.prototype;

import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;

/**
 * @author Fan HaoJie
 * @Description: 教师模型
 * @date 2022/2/14 17:23
 */
@Slf4j
public class TeacherShallowCloneMode implements Cloneable {

    String name;
    StudentMode studentMode;

    public TeacherShallowCloneMode(String name, StudentMode studentMode) {
        this.name = name;
        this.studentMode = studentMode;
    }

    public void setStudentMode(StudentMode studentMode) {
        this.studentMode = studentMode;
    }

    public StudentMode getStudentMode() {
        return studentMode;
    }

    public void outInfo() {
        log.info(this.name + this.studentMode.toString());
    }

    @SneakyThrows
    @Override
    public TeacherShallowCloneMode clone() {
        return (TeacherShallowCloneMode) super.clone();
    }

    public static void main(String[] args) {
        StudentMode stu1 = new StudentMode("老胡:", "摸金校尉");
        TeacherShallowCloneMode teacherShallowCloneMode = new TeacherShallowCloneMode("法外狂徒", stu1);
        TeacherShallowCloneMode teacherShallowCloneModeClone = teacherShallowCloneMode.clone();
        StudentMode studentMode = teacherShallowCloneModeClone.getStudentMode();
        studentMode.setName("瓦坎达");
        teacherShallowCloneMode.outInfo();
        teacherShallowCloneModeClone.outInfo();
        if (teacherShallowCloneMode.getStudentMode() == teacherShallowCloneModeClone.getStudentMode()){
            log.info("TeacherShallowCloneMode 浅克隆后 镶嵌对象为同一对象,内存地址一致");
        }
    }
}

-- log output --
17:41:12.810 [main] INFO com.server.entity.prototype.StudentMode - 学生原型创建成功
17:41:12.812 [main] INFO com.server.entity.prototype.TeacherShallowCloneMode - 法外狂徒StudentMode{name='瓦坎达', description='摸金校尉'}
17:41:12.812 [main] INFO com.server.entity.prototype.TeacherShallowCloneMode - 法外狂徒StudentMode{name='瓦坎达', description='摸金校尉'}
17:41:12.812 [main] INFO com.server.entity.prototype.TeacherShallowCloneMode - TeacherShallowCloneMode 浅克隆后 镶嵌对象为同一对象,内存地址一致

对于此种对象,发现镶嵌的类型并未被clone,仍指向原有属性所指向的对象的内存地址。

更改clone方法:

   @SneakyThrows
    @Override
    public TeacherShallowCloneMode clone() {
        TeacherShallowCloneMode teacherShallowCloneMode = (TeacherShallowCloneMode) super.clone();
        teacherShallowCloneMode.setStudentMode(teacherShallowCloneMode.getStudentMode().clone());
        return teacherShallowCloneMode;
    }

-- log output ---
17:45:05.040 [main] INFO com.server.entity.prototype.StudentMode - 学生原型创建成功
17:45:05.042 [main] INFO com.server.entity.prototype.StudentMode - 学生原型 Clone 成功
17:45:05.042 [main] INFO com.server.entity.prototype.TeacherShallowCloneMode - 法外狂徒StudentMode{name='老胡:', description='摸金校尉'}
17:45:05.042 [main] INFO com.server.entity.prototype.TeacherShallowCloneMode - 法外狂徒StudentMode{name='瓦坎达', description='摸金校尉'}

此时引用的其他对象也会被克隆,不再指向原有对象地址。

原型模式的应用场景

原型模式通常适用于以下场景。

  • 对象之间相同或相似,即只是个别的几个属性不同的时候。
  • 创建对象成本较大,例如初始化时间长,占用CPU太多,或者占用网络资源太多等,需要优化资源。
  • 创建一个对象需要繁琐的数据准备或访问权限等,需要提高性能或者提高安全性。
  • 系统中大量使用该类对象,且各个调用者都需要给它的属性重新赋值。

在 Spring中,原型模式应用的非常广泛,例如 scope='prototype'、JSON.parseObject() 等都是原型模式的具体应用。

原型模式的扩展

原型模式可扩展为带原型管理器的原型模式,它在原型模式的基础上增加了一个原型管理器 PrototypeManager 类。该类用 HashMap 保存多个复制的原型,Client 类可以通过管理器的 get(String id) 方法从中获取复制的原型。

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇