Java设计模式之单例和原型

今天这篇文章我们来学习创建型设计模式的另外两个孪生兄弟,单例和原型,其中原型设计模式中我们深入到JVM的内存模型,最后顺便谈谈Java中的值传递和引用传递。

上篇文章老王买产品 我们从最原始的基本实现方法,到简单(静态)工厂,然后使用工厂方法设计模式进行改造,最后考虑产品会产生变体,我们又扩展到了抽象工厂。

设计模式所有的相关代码均已上传到码云 读者可以自行下载学习测试。

一、引出问题

今天老王又来了,还是想买我们的产品,今天老王上老就提出来一个要求,当他购买产品的时候,每次都要从货架上给他拿相同的一个。

如果用传统实现方式,当老王拿到产品以后,直接和上一个比对一下就行了,如果不一致老王就还回来。

但通过我们查阅软件的七大设计原则 ,这很明显违反了依赖倒置原则,为了避免耦合和让代码更易于维护,老王是不能依赖具体产品的。

二、单例

我们就需要将产品比对在创建产品的时候进行判断,老王就只管拿。

老王来之前应该还有两种情况,一种就是老王还没来,产品就准备好了,也即饿汉式。第二种就是老王什么时候来,什么时候给他准备产品,也即懒汉式。

我们看具体的实现代码:

懒汉式:

/**
 * 懒汉式
 * @author tcy
 * @Date 29-07-2022
 */
public class LazySingletonProduct {

    private static volatile LazySingletonProduct instance=null;

    private LazySingletonProduct(){}

    public static synchronized LazySingletonProduct getInstance(){
        if (instance==null){
            instance=new LazySingletonProduct();

        }
        return instance;
    }

饿汉式:

/**
 * 饿汉式
 * @author tcy
 * @Date 29-07-2022
 */
public class HungrySingletonProduct {
    private static volatile HungrySingletonProduct instance=new HungrySingletonProduct();

    private HungrySingletonProduct(){};

    public static synchronized HungrySingletonProduct getInstance(){
        if (instance==null){
            instance=new HungrySingletonProduct();
        }
        return instance;
    }
}

老王类:

/**
 * @author tcy
 * @Date 29-07-2022
 */
public class Client {
    public static void main(String[] args) {
        HungrySingletonProduct instance1 = HungrySingletonProduct.getInstance();
        HungrySingletonProduct instance2 = HungrySingletonProduct.getInstance();

        if (instance1==instance2){
            System.out.println("我俩一样...");
        }else {
            System.out.println("我俩不一样...");
        }
    }
}

以上就是单例设计模式中的懒汉式和饿汉式,应该是设计模式中最简单的一个,理解起来难度也不大。

为了克服老王和他儿子小王一起来拿错的尴尬,我们在方法上加synchronized锁,对象引用上加volatile共享变量,但这样会带来效率问题,如果不考虑多线程需求,读者可自行去掉。

三、原型

老王今天很明显是找茬,他继续说,如果我不想要一个了,我要每次买都要不同的,你看着办。

每次创建产品都要不同的,传统的方式肯定就是重新new一个对象,但每创建一个对象都是一个复杂的过程,而且这样还会带来一定的代码冗余。

这就需要用到创建型设计模式中的原型模式中的拷贝,其中又分为浅拷贝和深拷贝。

我们先看基本概念。

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

这段意思也就是,老王购买产品的时候,如果产品都是基本数据类型(byte(位)、short(短整数)、int(整数)、long(长整数)、float(单精度)、double(双精度)、char(字符)和boolean(布尔值))和String,那么我们就使用浅拷贝。

如果产品包括别的产品(对象)的引用类型就要使用深拷贝。

如果想搞明白,为什么造成深拷贝和浅拷贝这个问题,我们就要重点说说JVM的内存模型。

我们声明一个基本数据类型的变量a=2,实际上是在栈中直接存储了一个a=2,当拷贝的时候直接把值拷贝过去,也就是直接有了一份a的副本。

当我们创建一个对象时Student stu=new Student(),实际上对象的值存储在堆中,在栈中只存放了stu="对象地址",stu指向了堆中的地址,jvm拷贝的时候只复制了栈中的地址,实际上他们堆中的对象还是一个。

我们再来看String类型。String 存在于堆内存、常量池;这种比较特殊, 传递是引用地址;由本身的final性, 每次赋值都是一个新的引用地址,原对象的引用和副本的引用互不影响。因此String就和基本数据类型一样,表现出了"深拷贝"特性。

我们具体看实现代码:

浅拷贝类:

/**
 * @author tcy
 * @Date 29-07-2022
 */
public class ShallowProduct implements Cloneable{

    private String name;

    private int num;

    public void show(){
        System.out.println("这是浅产品..."+name+"数量:"+num);
    }

    public String getName() {
        return name;
    }

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

    public int getNum() {
        return num;
    }

    public ShallowProduct setNum(int num) {
        this.num = num;
        return this;
    }

    @Override
    public ShallowProduct clone() throws CloneNotSupportedException {
        return (ShallowProduct) super.clone();
    }
}

如果需要哪个对象浅拷贝,需要该对象实现Cloneable接口,并重写clone()方法。

public void shallowTest()throws CloneNotSupportedException{
    ShallowProduct product1=new ShallowProduct();
    ShallowProduct product2 = product1.clone();
    product1.setName("老王");
    product2.setName("老李");

    product1.setNum(1);
    product2.setNum(2);

    product1.show();

    product2.show();
}

调用时输出的对象中的值直接就是两个不同的对象,实现了对象的浅拷贝。

如果该对象中包括引用类型呢?那怎么实现呢。

其实原理上也是很简单的,只需要将非基本数据类型也像浅拷贝那样操做就行了,然后在当前clone()方法中,调用非基本数据类型的clone()方法

深拷贝引用类:

/**
 * @author tcy
 * @Date 29-07-2022
 */
public class Child implements Cloneable{

    private String childName;

    public String getChildName() {
        return childName;
    }

    public Child setChildName(String childName) {
        this.childName = childName;
        return this;
    }

    @Override
    protected Child clone() throws CloneNotSupportedException {
        return (Child) super.clone();
    }
}

深拷贝类:

/**
 * @author tcy
 * @Date 29-07-2022
 */
public class DeepProduct implements Cloneable{

    private String name;

    private Integer num;

    private Child child;
    public String getName() {
        return name;
    }

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

    public Integer getNum() {
        return num;
    }

    public DeepProduct setNum(Integer num) {
        this.num = num;
        return this;
    }

    public void show(){
        System.out.println("这是深产品..."+name+"数量:"+num+"包括child:"+child.getChildName());
    }

    @Override
    public DeepProduct clone() throws CloneNotSupportedException {
        DeepProduct clone = (DeepProduct) super.clone();
        clone.child=child.clone();
        return clone;
    }

    public Child getChild() {
        return child;
    }

    public DeepProduct setChild(Child child) {
        this.child = child;
        return this;
    }
}

我们测试一下对象中的值是否发生了改变。

public void deepTest() throws CloneNotSupportedException {
    DeepProduct product1=new DeepProduct();
    Child child=new Child();
    child.setChildName("老王child");

    product1.setName("老王");
    product1.setNum(1);
    product1.setChild(child);

    //--------------
    DeepProduct product2=product1.clone();
    product2.setName("老李");
    product2.setNum(2);
    product2.getChild().setChildName("老李child");

    product1.show();
    product2.show();
}

老李、老王都正确的输出了,说明实现没有问题。

这样就符合了老王的要求。

既然说到了jvm的内存模型,就有必要说一下java中的值传递和引用传递。

实际上java应该就是值传递,在调用方法的时候,如果参数是基本数据类型,那么传递的就是副本,我们在方法中无论怎么给他赋值,他原本的值都不会有变化。

在调用方法的时候,如果参数是引用数据类型,那么传递的就是这个对象的地址,我们在方法中修改这个对象都会影响他原本的对象。

造成这个现象的原因其实是和浅拷贝、深拷贝的原理是一样的,都是栈、堆内存的结构导致的。

老王看他的要求都满足了,最后心满意足的拿着产品走了。

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对我们的支持。如果你想了解更多相关内容请查看下面相关链接

(0)

相关推荐

  • Java创建型设计模式之抽象工厂模式(Abstract Factory)

    目录 抽象工厂模式 概述 产品等级结构与产品族 优缺点 主要角色 抽象工厂模式的基本使用 创建抽象产品 创建具体产品 创建抽象工厂 创建具体工厂 客户端执行 抽象工厂模式 概述 抽象工厂模式(Abastract Factory Pattern)属于创建型模式,它提供了一种创建对象的最佳方式. 它提供一个创建一系列相关或相互依赖对象的接口,无须显式指定他们具体的类.每个生成的工厂都能按照工厂模式提供对象. 抽象工厂模式是围绕一个超级工厂创建其他工厂,该超级工厂又称为其他工厂的工厂. 产品等级结构与

  • Java结构型设计模式中建造者模式示例详解

    目录 建造者模式 概述 角色 优缺点 应用场景 基本使用 创建产品类 创建建造者类 使用 链式写法 创建产品类与建造者类 使用 建造者模式 概述 建造者模式(Builder Pattern)属于创建型模式. 它是将一个复杂的构建与其表示相分离,使得同样的构建过程可以创建不同的表示. 简而言之:建造者模式就是使用多个简单的对象一步一步构建成一个复杂的对象. 建造者模式适用于创建对象需要很多步骤,但是步骤的顺序不一定固定.如果一个对象有非常复杂的内部结构(很多属性),可以将复杂对象的创建和使用进行分

  • Java创建型设计模式之工厂方法模式深入详解

    目录 简单工厂模式 定义产品对象 创建工厂类 工厂使用反射 工厂方法模式 概述 应用场景 优缺点 主要角色 工厂方法模式的基本使用 创建抽象产品 创建具体产品 创建抽象工厂 创建具体工厂 客户端执行 简单工厂模式 简单工厂模式(Simple Factory Pattern)是指由一个工厂对象决定创建出哪一种产品类的实例,但是它不属于设计模式. 简单工厂适用于工厂类负责创建的对象较少的场景,且客户端只需要传入工厂类的参数,对于如何创建对象的逻辑不需要关心. 定义产品对象 public interf

  • Java结构型设计模式中代理模式示例详解

    目录 代理模式 分类 主要角色 作用 静态代理与动态代理的区别 静态代理的基本使用 创建抽象主题 创建真实主题 创建代理主题 客户端调用 JDK动态代理的基本使用 创建抽象主题 创建真实主题 创建代理主题 客户端调用 小优化 CGLIB动态代理的基本使用 创建抽象主题 创建真实主题 创建代理主题 客户端调用 小优化 CGLIB与JDK动态代理区别 1.执行条件 2.实现机制 3.性能 代理模式 代理模式(Proxy Pattern)属于结构型模式. 它是指为其他对象提供一种代理以控制对这个对象的

  • Java结构型设计模式之享元模式示例详解

    目录 享元模式 概述 目的 应用场景 优缺点 主要角色 享元模式结构 内部状态和外部状态 享元模式的基本使用 创建抽象享元角色 创建具体享元角色 创建享元工厂 客户端调用 总结 享元模式实现数据库连接池 创建数据库连接池 使用数据库连接池 享元模式 概述 享元模式(Flyweight Pattern)又称为轻量级模式,是对象池的一种实现.属于结构型模式. 类似于线程池,线程池可以避免不停的创建和销毁多个对象,消耗性能.享元模式提供了减少对象数量从而改善应用所需的对象结构的方式. 享元模式尝试重用

  • Java设计模式之工厂方法和抽象工厂

    全网最详细的工厂设计模式,本文主要是创建型设计模式中的工厂方法和抽象工厂,先由传统实现方式引出问题,接着对代码改进到简单工厂,后扩展到工厂方法,最后是抽象工厂模式,文中包括概念理解和相关实现代码. 读者可以拉取完整代码本地学习,实现代码均测试通过上传到码云 一.引出问题 如果有一个客户老王,需要购买产品,产品分别是A.B.C. 如果用传统方法实现,分别定义A.B.C三个类,再分别创建他们所属的方法. 在客户对象中再分别调用他们的方法. Product ClientProduct(String o

  • Java设计模式中的门面模式详解

    目录 门面模式 概述 应用场景 目的 优缺点 主要角色 门面模式的基本使用 创建子系统角色 创建外观角色 客户端调用 门面模式实现商城下单 库存系统 支付系统 物流系统 入口系统 客户端调用 门面模式 概述 门面模式(Facade Pattern)又叫外观模式,属于结构性模式. 它提供一个统一的接口去访问多个子系统的多个不同的接口,它为子系统中的一组接口提供一个统一的高层接口.使得子系统更容易使用. 客户端不需要知道系统内部的复杂联系,只需定义系统的入口.即在客户端和复杂系统之间再加一层,这一层

  • Java结构型设计模式之组合模式详解

    目录 组合模式 应用场景 优缺点 主要角色 组合模式结构 分类 透明组合模式 创建抽象根节点 创建树枝节点 创建叶子节点 客户端调用 安全组合模式 创建抽象根节点 创建树枝节点 创建叶子节点 客户端调用 组合模式 组合模式(Composite Pattern)也称为整体-部分(Part-Whole)模式,属于结构型模式. 它的宗旨是通过将单个对象(叶子节点)和组合对象(树枝节点)用相同的接口进行表示,使得客户端对单个对象和组合对象的使用具有一致性. 组合模式一般用来描述整体与部分的关系,它将对象

  • Java设计模式之单例和原型

    今天这篇文章我们来学习创建型设计模式的另外两个孪生兄弟,单例和原型,其中原型设计模式中我们深入到JVM的内存模型,最后顺便谈谈Java中的值传递和引用传递. 上篇文章老王买产品 我们从最原始的基本实现方法,到简单(静态)工厂,然后使用工厂方法设计模式进行改造,最后考虑产品会产生变体,我们又扩展到了抽象工厂. 设计模式所有的相关代码均已上传到码云 读者可以自行下载学习测试. 一.引出问题 今天老王又来了,还是想买我们的产品,今天老王上老就提出来一个要求,当他购买产品的时候,每次都要从货架上给他拿相

  • java 设计模式之单例的实例详解

    java 设计模式之单例的实例详解 设计模式思想 什么是设计模式:我作为初学者,今天第一次正式学习设计模式,我觉得对与理解什么是设计模式很重要,那么什么是设计模式呢? 设计模式:解决问题的一种行之有效的思想. 设计模式:用于解决特定环境下.重复出现的特定问题的解决方案 我的理解是前人在软件设计的时候碰到了一类问题,他们总结出了一套行之有效,并且经过验证的解决方案. 设计模式的优点: 1.设计模式都是一些相对优秀的解决方案,很多问题都是典型的.有代表性的问题,学习设计模式,我们就不用自己从头来解决

  • JAVA 静态的单例的实例详解

    JAVA  静态的单例的实例详解 实现代码: public class Printer { private Printer(){ } public static Printer newInstance(){ return CreatePrinter.mPrinter; } private static class CreatePrinter{ private final static Printer mPrinter = new Printer(); } } 因为静态的单例对象没有作为类的成员变

  • Java静态内部类实现单例过程

    这篇文章主要介绍了Java静态内部类实现单例过程,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 枚举实现单例 线程安全,调用效率高,不能延时加载,可以天然的防止反射和反序列化调用 public enum SingletonFactory { //枚举元素本身就是单例 INSTANCE; //添加自己需要的操作 public SingletonObject getInstance(){ return new SingletonObject();

  • JAVA  静态的单例的实例详解

    JAVA  静态的单例的实例详解 实现代码: public class Printer { private Printer(){ } public static Printer newInstance(){ return CreatePrinter.mPrinter; } private static class CreatePrinter{ private final static Printer mPrinter = new Printer(); } } 因为静态的单例对象没有作为类的成员变

  • php设计模式之单例、多例设计模式的应用分析

    单例(Singleton)模式和不常见的多例(Multiton)模式控制着应用程序中类的数量.如模式名称,单例只能实例化一次,只有一个对象,多例模式可以多次实例化. 基于Singleton的特性,我们经常用Singleton配置应用程序并定义应用程序中可能随时访问的变量.但有时并不推荐使用Singleton,因为它生成了一个全局状态且 该单一根对象没有封装任何系统功能.多数情况下,会使单元测试和调试变得困难.读者根据情况自行决定.代码示例: 复制代码 代码如下: <?phpclass Singl

  • 浅谈Java编程中的单例设计模式

    写软件的时候经常需要用到打印日志功能,可以帮助你调试和定位问题,项目上线后还可以帮助你分析数据.但是Java原生带有的System.out.println()方法却很少在真正的项目开发中使用,甚至像findbugs等代码检查工具还会认为使用System.out.println()是一个bug. 为什么作为Java新手神器的System.out.println(),到了真正项目开发当中会被唾弃呢?其实只要细细分析,你就会发现它的很多弊端.比如不可控制,所有的日志都会在项目上线后照常打印,从而降低运

  • java 中设计模式之单例

    java 中设计模式之单例 设计模式思想 什么是设计模式:我作为初学者,今天第一次正式学习设计模式,我觉得对与理解什么是设计模式很重要,那么什么是设计模式呢? 设计模式:解决问题的一种行之有效的思想. 设计模式:用于解决特定环境下.重复出现的特定问题的解决方案 我的理解是前人在软件设计的时候碰到了一类问题,他们总结出了一套行之有效,并且经过验证的解决方案. 设计模式的优点: 1.设计模式都是一些相对优秀的解决方案,很多问题都是典型的.有代表性的问题,学习设计模式,我们就不用自己从头来解决这些问题

  • 详解java中的6种单例写法及优缺点

    在java中,单例有很多种写法,面试时,手写代码环节,除了写算法题,有时候也会让手写单例模式,这里记录一下单例的几种写法和优缺点. 1.初级写法 2.加锁 3.饿汉式 4.懒汉式 5.双锁检验 6.内部类 1.初级写法 package com.java4all.test6; /** * Author: yunqing * Date: 2018/8/13 * Description:单例模式 -- 初级 */ public class Singleton { private static Sing

  • Java双重校验锁单例原理

    目录 前言 正文 代码实现 总结 前言 作为开发者,单例这个就再也熟悉不过了,但是作为多种单例实现模式,我个人觉得双重校验锁是非常不多的实现,我们简单来分析一下其原理. 正文 先来说一下Java版本的,后面会涉及Kotlin中的代码我们再做比对. 代码实现 Java代码实现如下: //双重校验锁单例 public class SingleInstance { //必须volatile修饰 见分析1 private volatile static SingleInstance instance;

随机推荐