大家好,欢迎来到IT知识分享网。
一、原型模式
原型模式是通过给出一个原型对象来指明所创建的对象的类型,然后使用自身实现的克隆接口来复制这个原型对象,该模式就是用这种方式来创建出更多同类型的对象
原型模式是在内存二进制流的拷贝,要被直接new一个对象性能好很多,特别是要在一个循环体内产生大量的对象时,原型模式可以更好地体现其优点
1、实现原型模式
//实现Cloneable接口的原型抽象类Prototype
public class Prototype implements Cloneable {
//重写clone()方法
@Override
protected Object clone() {
Prototype prototype = null;
try {
prototype = (Prototype) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return prototype;
}
}
public class ConcretePrototype extends Prototype{
public void show (){
System.out.println("原型模型实现类");
}
}
public class Client {
public static void main(String[] args) {
ConcretePrototype cp = new ConcretePrototype();
for (int i = 0; i < 10; ++i) {
ConcretePrototype cloneCp = (ConcretePrototype) cp.clone();
cloneCp.show();
}
}
}
要实现一个原型类,需要具备三个条件:
- 实现Cloneable接口:Cloneable接口与序列化接口的作用类似,它只是告诉虚拟机可以安全地在实现了这个接口的类上使用
clone()
方法。在JVM中,只有实现了Cloneable接口的类才可以被拷贝,否则会抛出CloneNotSupportedException异常 - 重写Object类中的
clone()
方法:在Java中,所有类的父类都是Object类,而Object类中有一个clone()
方法,作用是返回对象的一个拷贝 - 在重写的
clone()
方法中调用super.clone()
:默认情况下,类不具备复制对象的能力,需要调用super.clone()
来实现
2、原型模式的注意事项
1)、构造函数不会被执行
由于Object类的clone()
方法的原理是从堆内存中以二进制流的方式进行拷贝,重新分配一个内存块,所以对象拷贝时构造函数不会被执行
//实现Cloneable接口的原型抽象类Prototype
public class Prototype implements Cloneable {
public Prototype(){
System.out.println("Prototype的构造函数执行了...");
}
//重写clone()方法
@Override
protected Object clone() {
Prototype prototype = null;
try {
prototype = (Prototype) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return prototype;
}
}
public class ConcretePrototype extends Prototype{
public ConcretePrototype(){
System.out.println("ConcretePrototype的构造函数执行了...");
}
public void show (){
System.out.println("原型模型实现类");
}
}
public class Client {
public static void main(String[] args) {
ConcretePrototype cp = new ConcretePrototype();
ConcretePrototype cloneCp = (ConcretePrototype) cp.clone();
}
}
执行结果如下:
Prototype的构造函数执行了...
ConcretePrototype的构造函数执行了...
2)、浅拷贝和深拷贝
Object类提供的clone()
方法只是拷贝本对象,其对象内部的数组、引用对象等都不拷贝,还是指向原生对象的内部元素地址,这种拷贝就叫做浅拷贝
public class Prototype implements Cloneable {
private List<String> list = new ArrayList<>();
@Override
protected Object clone() {
Prototype prototype = null;
try {
prototype = (Prototype) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return prototype;
}
public void setValue(String value) {
this.list.add(value);
}
public List<String> getValue() {
return this.list;
}
}
public class Client {
public static void main(String[] args) {
Prototype prototype=new Prototype();
prototype.setValue("张三");
Prototype clonePrototype = (Prototype) prototype.clone();
clonePrototype.setValue("李四");
System.out.println(prototype.getValue());
}
}
执行结果如下:
[张三, 李四]
深拷贝就是基于浅拷贝来递归实现具体的每个对象
public class Prototype implements Cloneable {
private ArrayList<String> list = new ArrayList<>();
@Override
protected Object clone() {
Prototype prototype = null;
try {
prototype = (Prototype) super.clone();
//ArrayList实现了Cloneable接口,List没有实现
this.list = (ArrayList) this.list.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return prototype;
}
public void setValue(String value) {
this.list.add(value);
}
public List<String> getValue() {
return this.list;
}
}
执行结果如下:
[张三]
3、适用场景
在一些重复创建对象的场景下,可以使用原型模式来提高对象的创建性能。例如,在循环体内创建对象时,就可以考虑用clone的方式来实现
二、享元模式
享元模式是运用共享技术有效地最大限度地复用细粒度对象的一种模式。该模式中,以对象的信息状态划分,可以分为内部数据和外部数据。内部数据是对象可以共享出来的信息,这些信息不会随着系统的运行而改变;外部数据则是在不同运行时被标记了不同的值
享元模式一般可以分为三个角色,分别为 Flyweight(抽象享元类)、ConcreteFlyweight(具体享元类)和 FlyweightFactory(享元工厂类)。抽象享元类通常是一个接口或抽象类,向外界提供享元对象的内部数据或外部数据;具体享元类是指具体实现内部数据共享的类;享元工厂类则是主要用于创建和管理享元对象的工厂类
1、实现享元模式
//抽象享元类
public interface Flyweight {
//对外状态对象
void operation(String name);
//对内对象
String getType();
}
//具体享元类
public class ConcreteFlyweight implements Flyweight {
private String type;
public ConcreteFlyweight(String type) {
this.type = type;
}
@Override
public void operation(String name) {
System.out.printf("[类型(内在状态)] - [%s] - [名字(外在状态)] - [%s]\n", type, name);
}
@Override
public String getType() {
return type;
}
}
//享元工厂类
public class FlyweightFactory {
private static final Map<String, Flyweight> FLYWEIGHT_MAP = new HashMap<>();//享元池,用来存储享元对象
public static Flyweight getFlyweight(String type) {
if (FLYWEIGHT_MAP.containsKey(type)) {
//如果在享元池中存在对象,则直接获取
return FLYWEIGHT_MAP.get(type);
} else {
//在响应池不存在,则新创建对象,并放入到享元池
ConcreteFlyweight flyweight = new ConcreteFlyweight(type);
FLYWEIGHT_MAP.put(type, flyweight);
return flyweight;
}
}
}
public class Client {
public static void main(String[] args) {
Flyweight fw0 = FlyweightFactory.getFlyweight("a");
Flyweight fw1 = FlyweightFactory.getFlyweight("b");
Flyweight fw2 = FlyweightFactory.getFlyweight("a");
Flyweight fw3 = FlyweightFactory.getFlyweight("b");
fw1.operation("abc");
System.out.printf("[结果(对象对比)] - [%s]\n", fw0 == fw2);
System.out.printf("[结果(内在状态)] - [%s]\n", fw1.getType());
}
}
执行结果如下:
[类型(内在状态)] - [b] - [名字(外在状态)] - [abc]
[结果(对象对比)] - [true]
[结果(内在状态)] - [b]
如果对象已经存在于享元池中,则不会再创建该对象了,而是共用享元池中内部数据一致的对象。这样就减少了对象的创建,同时也节省了同样内部数据的对象所占用的内存空间
2、适用场景
享元模式在实际开发中的应用也非常广泛。例如Java的String字符串,在一些字符串常量中,会共享常量池中字符串对象,从而减少重复创建相同值对象,占用内存空间。代码如下:
String s1 = "hello";
String s2 = "hello";
System.out.println(s1 == s2);//true
还有,在日常开发中的应用。例如,线程池就是享元模式的一种实现;将商品存储在应用服务的缓存中,那么每当用户获取商品信息时,则不需要每次都从redis缓存或者数据库中获取商品信息,并在内存中重复创建商品信息了
单例模式和享元模式都是为了避免重复创建对象,你知道这两者的区别在哪儿吗?
首先,这两种设计模式的实现方式是不同的。我们使用单例模式是避免每次调用一个类实例时,都要重复实例化该实例,目的是在类本身获取实例化对象的唯一性;而享元模式则是通过一个共享容器来实现一系列对象的共享
其次,两者在使用场景上也是有区别的。单例模式更多的是强调减少实例化提升性能,因此它一般是使用在一些需要频繁创建和销毁实例化对象,或创建和销毁实例化对象非常消耗资源的类中
例如,连接池和线程池中的连接就是使用单例模式实现的,数据库操作是非常频繁的,每次操作都需要创建和销毁连接,如果使用单例,可以节省不断新建和关闭数据库连接所引起的性能消耗。而享元模式更多的是强调共享相同对象或对象属性,以此节约内存使用空间
除了区别,这两种设计模式也有共性,单例模式可以避免重复创建对象,节约内存空间,享元模式也可以避免一个类的重复实例化。总之,两者很相似,但侧重点不一样,假如碰到一些要在两种设计模式中做选择的场景,我们就可以根据侧重点来选择
参考:
极客时间专栏《Java性能调优实战》
《设计模式之禅》
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://yundeesoft.com/13655.html