当前位置: 首页 > 后端技术 > Java

设计模式之Singleton与Prototype

时间:2023-04-01 14:20:34 Java

今天这篇文章,我们来了解下创造型设计模式的另外两个孪生兄弟Singleton与Prototype。在原型设计模式中,我们深入JVM的内存模型,最后顺便说一下。Java中按值传递和按引用传递。上一篇老王买了一个产品。我们从最原始的基本实现方法,到一个简单的(静态)工厂,再用工厂方法设计模式进行改造。最后,考虑到产品会产生变体,我们扩展到抽象工厂。设计模式相关代码已全部上传至码云,读者可自行下载学习测试。1.导致问题今天老王又来了,还是要买我们的产品。今天老王提出一个要求,每次买产品,一定要从货架上拿一样的。如果采用传统的实现方式,老王拿到产品的时候,可以直接和之前的进行对比。如有不符,老王退货。但是通过我们对软件七大设计原则的回顾,这显然违背了依赖倒置原则。为了避免耦合,让代码更容易维护,老王不能依赖具体的产品。2.单个case,我们在创建产品的时候需要对比产品,做出判断,老王就拿了。老王来之前应该有两种情况。一是产品在老王来之前就做好了,就是饿了么中国风。二是什么时候老王会来给他准备产品,也就是懒人风格。我们看具体的实例代码:懒汉式:/***懒汉式*@authortcy*@Date29-07-2022*/publicclassLazySingletonProduct{privatestaticvolatileLazySingletonProductinstance=null;privateLazySingletonProduct(){}publicstaticsynchronizedLazySingletonProductgetInstance(){if(instance==null){instance=newLazySingletonProduct();}返回实例;}饿汉式:/***饿汉式*@authortcy*@Date29-07-2022*/publicclassHungrySingletonProduct{privatestaticvolatileHungrySingletonProductinstance=newHungrySingletonProduct();私有HungrySingletonProduct(){};publicstaticsynchronizedHungrySingletonProductgetInstance(){if(instance==null){instance=newHungrySingletonProduct();}返回实例;}}老王类:/***@authortcy*@Date29-07-2022*/publicclassClient{publicstaticvoidmain(String[]args){HungrySingletonProductinstance1=HungrySingletonProduct.getInstance();HungrySingletonProductinstance2=HungrySingletonProduct.getInstance();if(instance1==instance2){System.out.println("我们是一样的...");}else{System.out.println("我们不一样..");}}}以上就是单例设计模式中的懒人风格和饿汉风格。应该是设计模式中最简单的一个,理解起来也不难。为了一起战胜老王和他的儿子小王,错的很尴尬。我们给方法加synchronized锁,给对象引用加volatile共享变量,但是这样会带来效率问题。如果不考虑多线程需求,读者可以自行去除。3、原型法老今天明显是找茬。他接着说,如果我不要了,我每次都会买不同的。你可以弄清楚。每次创建产品时,它都必须不同。传统的方式肯定是创建一个新的对象,但是每次创建一个对象都是一个复杂的过程,这也会带来一定的代码冗余。这就需要在创意设计模式中使用原型模式中的复制,分为浅复制和深复制。让我们先看看基本概念。浅克隆:创建一个新的对象,该对象的属性与原对象完全相同。对于非基本类型属性,它们仍然指向原始属性所指向的内存地址。深克隆:新建一个对象,属性中的引用类型也会被克隆,不再指向原属性所指向的内存地址。即老王购买产品时,如果产品是基本数据类型(byte(位)、short(短整数)、int(整数)、long(长整数)、float(单精度)、double(双精度),char(字符)和boolean(布尔值))和String,那么我们使用浅拷贝。如果产品包含其他产品(对象)的引用类型,则使用深拷贝。要想理解深拷贝和浅拷贝的问题为什么会产生,就必须把目光放在JVM的内存模型上。我们声明了一个基本数据类型的变量a=2,其实就是直接把a=2存入栈中,复制的时候直接把值复制过来,也就是直接有a的副本。当我们创建一个对象Studentstu=newStudent()时,这个对象的值实际上是存放在堆中的,而栈中存放的只是stu="objectaddress",stu指向的是堆中的地址。jvm复制的时候,只是复制了栈中的地址,但是他们堆中的对象其实是一个。我们再看看String类型。String存在于堆内存和常量池中;这个比较特殊,转账是参考地址;由于其最终性,每次赋值都是一个新的引用地址,原对象的引用和副本的引用互不影响。因此,String和基本数据类型一样,表现出“深拷贝”的特性。下面具体看一下实现代码:浅拷贝类:/***@authortcy*@Date29-07-2022*/publicclassShallowProductimplementsCloneable{privateStringname;私人整数;publicvoidshow(){System.out.println("这是浅积..."+name+"数量:"+num);}publicStringgetName(){返回名称;}publicShallowProductsetName(Stringname){this.name=name;归还这个;}publicintgetNum(){返回数字;}publicShallowProductsetNum(intnum){this.num=num;归还这个;}@OverridepublicShallowProductclone()抛出CloneNotSupportedException{return(ShallowProduct)super.clone();如果你需要一个对象的浅拷贝,你需要该对象实现Cloneable接口并重写clone()方法。publicvoidshallowTest()throwsCloneNotSupportedException{ShallowProductproduct1=newShallowProduct();ShallowProductproduct2=product1.clone();product1.setName("老王");product2.setName("老李");product1.setNum(1);product2.setNum(2);product1.show();product2.show();}调用时输出对象中的值直接是两个不同的对象,实现了对象的浅拷贝。如果对象包含引用类型怎么办?那么如何实现呢。其实原理也很简单,只需要像浅拷贝一样操作非基本数据类型,然后在当前的clone()方法中,调用非基本数据类型的clone()方法即可深拷贝参考类:/***@authortcy*@Date29-07-2022*/publicclassChildimplementsCloneable{privateStringchildName;publicStringgetChildName(){返回子名;}publicChildsetChildName(StringchildName){this.childName=childName;归还这个;}@OverrideprotectedChildclone()throwsCloneNotSupportedException{return(Child)super.clone();}}深拷贝类:/***@authortcy*@Date29-07-2022*/publicclassDeepProductimplementsCloneable{privateStringname;私有整数;私人儿童儿童;publicStringgetName(){返回名称;}publicDeepProductsetName(Stringname){this.name=name;归还这个;}publicIntegergetNum(){返回数字;}publicDeepProductsetNum(Integernum){this.num=num;归还这个;}publicvoidshow(){System.out.println("这是一个深度产品..."+name+"数量:"+num+"包括child:"+child.getChildName());}@OverridepublicDeepProductclone()throwsCloneNotSupportedException{DeepProductclone=(DeepProduct)super.clone();clone.child=child.clone();返回克隆;}publicChildgetChild(){返回孩子;}publicDeepProductsetChild(Childchild){this.child=child;归还这个;}}让我们测试对象中的值是否已经改变publicvoiddeepTest()抛出CloneNotSupportedException{DeepProductproduct1=newDeepProduct();孩子孩子=新孩子();child.setChildName("老王孩子");product1.setName("老王");product1.setNum(1);product1.setChild(孩子);//------------DeepProductproduct2=product1.clone();product2.setName("老李");product2.setNum(2);product2.getChild().setChildName("老李孩子");product1.show();product2.show();}老李和老王都输出正确,说明执行没有问题。这倒是符合老王的要求。说完jvm的内存模型,就要说说java中的值传递和引用传递了。其实java应该是按值传递的。调用方法时,如果参数是基本数据类型,那么传递的是一个副本。不管我们在方法中如何给它赋值,它原来的值都不会改变。调用方法时,如果参数是引用数据类型,则传递对象的地址。如果我们在方法中修改了这个对象,就会影响它原来的对象。出现这种现象的原因其实和浅拷贝和深拷贝的原理是一样的,都是栈和堆内存的结构造成的。老王见自己的要求得到了满足,终于心满意足地拿着产品离开了。