今天这篇文章我们来学习创建型设计模式的另外两个孪生兄弟,单例和原型,其中原型设计模式中我们深入到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应该就是值传递,在调用方法的时候,如果参数是基本数据类型,那么传递的就是副本,我们在方法中无论怎么给他赋值,他原本的值都不会有变化。
在调用方法的时候,如果参数是引用数据类型,那么传递的就是这个对象的地址,我们在方法中修改这个对象都会影响他原本的对象。
造成这个现象的原因其实是和浅拷贝、深拷贝的原理是一样的,都是栈、堆内存的结构导致的。
老王看他的要求都满足了,最后心满意足的拿着产品走了。
我有一个模型:classItem项目有一个属性“商店”基于存储的值,我希望Item对象对特定方法具有不同的行为。Rails中是否有针对此的通用设计模式?如果方法中没有大的if-else语句,这是如何干净利落地完成的? 最佳答案 通常通过Single-TableInheritance. 关于ruby-on-rails-Rails-子类化模型的设计模式是什么?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.co
我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i
鉴于我有以下迁移:Sequel.migrationdoupdoalter_table:usersdoadd_column:is_admin,:default=>falseend#SequelrunsaDESCRIBEtablestatement,whenthemodelisloaded.#Atthispoint,itdoesnotknowthatusershaveais_adminflag.#Soitfails.@user=User.find(:email=>"admin@fancy-startup.example")@user.is_admin=true@user.save!ende
我将应用程序升级到Rails4,一切正常。我可以登录并转到我的编辑页面。也更新了观点。使用标准View时,用户会更新。但是当我添加例如字段:name时,它不会在表单中更新。使用devise3.1.1和gem'protected_attributes'我需要在设备或数据库上运行某种更新命令吗?我也搜索过这个地方,找到了许多不同的解决方案,但没有一个会更新我的用户字段。我没有添加任何自定义字段。 最佳答案 如果您想允许额外的参数,您可以在ApplicationController中使用beforefilter,因为Rails4将参数
给定一个复杂的对象层次结构,幸运的是它不包含循环引用,我如何实现支持各种格式的序列化?我不是来讨论实际实现的。相反,我正在寻找可能会派上用场的设计模式提示。更准确地说:我正在使用Ruby,我想解析XML和JSON数据以构建复杂的对象层次结构。此外,应该可以将该层次结构序列化为JSON、XML和可能的HTML。我可以为此使用Builder模式吗?在任何提到的情况下,我都有某种结构化数据-无论是在内存中还是文本中-我想用它来构建其他东西。我认为将序列化逻辑与实际业务逻辑分开会很好,这样我以后就可以轻松支持多种XML格式。 最佳答案 我最
目录前言滤波电路科普主要分类实际情况单位的概念常用评价参数函数型滤波器简单分析滤波电路构成低通滤波器RC低通滤波器RL低通滤波器高通滤波器RC高通滤波器RL高通滤波器部分摘自《LC滤波器设计与制作》,侵权删。前言最近需要学习放大电路和滤波电路,但是由于只在之前做音乐频谱分析仪的时候简单了解过一点点运放,所以也是相当从零开始学习了。滤波电路科普主要分类滤波器:主要是从不同频率的成分中提取出特定频率的信号。有源滤波器:由RC元件与运算放大器组成的滤波器。可滤除某一次或多次谐波,最普通易于采用的无源滤波器结构是将电感与电容串联,可对主要次谐波(3、5、7)构成低阻抗旁路。无源滤波器:无源滤波器,又称
项目介绍随着我国经济迅速发展,人们对手机的需求越来越大,各种手机软件也都在被广泛应用,但是对于手机进行数据信息管理,对于手机的各种软件也是备受用户的喜爱小学生兴趣延时班预约小程序的设计与开发被用户普遍使用,为方便用户能够可以随时进行小学生兴趣延时班预约小程序的设计与开发的数据信息管理,特开发了小程序的设计与开发的管理系统。小学生兴趣延时班预约小程序的设计与开发的开发利用现有的成熟技术参考,以源代码为模板,分析功能调整与小学生兴趣延时班预约小程序的设计与开发的实际需求相结合,讨论了小学生兴趣延时班预约小程序的设计与开发的使用。开发环境开发说明:前端使用微信微信小程序开发工具:后端使用ssm:VU
了解Rails缓存如何工作的人可以真正帮助我。这是嵌套在Rails::Initializer.runblock中的代码:config.after_initializedoSomeClass.const_set'SOME_CONST','SOME_VAL'end现在,如果我运行script/server并发出请求,一切都很好。然而,在我的Rails应用程序的第二个请求中,一切都因单元化常量错误而变得糟糕。在生产模式下,我可以成功发出第二个请求,这意味着常量仍然存在。我已通过将以上内容更改为以下内容来解决问题:config.after_initializedorequire'some_cl
我在我的项目中有一个用户和一个管理员角色。我使用Devise创建了身份验证。在我的管理员角色中,我没有任何确认。在我的用户模型中,我有以下内容:devise:database_authenticatable,:confirmable,:recoverable,:rememberable,:trackable,:validatable,:timeoutable,:registerable#Setupaccessible(orprotected)attributesforyourmodelattr_accessible:email,:username,:prename,:surname,:
我经常迷上ruby的一件事是递归模式。例如,假设我有一个数组,它可能包含无限深度的数组作为元素。所以,例如:my_array=[1,[2,3,[4,5,[6,7]]]]我想创建一个方法,可以将数组展平为[1,2,3,4,5,6,7]。我知道.flatten可以完成这项工作,但这个问题是作为我经常遇到的递归问题的一个例子-因此我试图找到一个更可重用的解决方案。简而言之-我猜这种事情有一个标准模式,但我想不出任何特别优雅的东西。任何想法表示赞赏 最佳答案 递归是一种方法,它不依赖于语言。您在编写算法时要考虑两种情况:再次调用函数的情