创建型模式
目录
1、原型模式
原型模式(Prototype Pattern)是用于创建重复的对象,同时又能保证性能。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。这种模式是实现了一个原型接口,该接口用于创建当前对象的克隆。当直接创建对象的代价比较大时,则采用这种模式。拷贝克隆比new快(不一定见得,只有new构造对象较为耗时或成本很高,才考虑原型模式)。
例如,一个对象需要在一个高代价的数据库操作之后被创建。我们可以缓存该对象,在下一个请求时返回它的克隆,在需要的时候更新数据库,以此来减少数据库调用。
意图:用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
主要解决:在运行期建立和删除原型。
何时使用:
1、当一个系统应该独立于它的产品创建,构成和表示时。
2、当要实例化的类是在运行时刻指定时,例如,通过动态装载。
3、为了避免创建一个与产品类层次平行的工厂类层次时。
4、当一个类的实例只能有几个不同状态组合中的一种时。建立相应数目的原型并克隆它们可能比每次用合适的状态手工实例化该类更方便一些。
如何解决:利用已有的一个原型对象,快速地生成和原型对象一样的实例。
关键代码:
1、实现克隆操作,在 JAVA 继承 Cloneable,重写 clone(),在 .NET 中可以使用 Object 类的 MemberwiseClone() 方法来实现对象的浅拷贝或通过序列化的方式来实现深拷贝。
2、原型模式同样用于隔离类对象的使用者和具体类型(易变类)之间的耦合关系,它同样要求这些"易变类"拥有稳定的接口。
应用实例: 1、细胞分裂。 2、JAVA 中的 Object clone() 方法。
1.1 原型模式UML图
1.2 日常生活中看原型模式
- 简历复印
- 细胞分裂
1.3 使用场景
- 1、资源优化场景。
- 2、类初始化需要消化非常多的资源,这个资源包括数据、硬件资源等。
- 3、性能和安全要求的场景。
- 4、通过 new 产生一个对象需要非常繁琐的数据准备或访问权限,则可以使用原型模式。
- 5、一个对象多个修改者的场景。
- 6、一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时,可以考虑使用原型模式拷贝多个对象供调用者使用。
- 7、在实际项目中,原型模式很少单独出现,一般是和工厂方法模式一起出现,通过 clone 的方法创建一个对象,然后由工厂方法提供给调用者。原型模式已经与 Java 融为浑然一体,大家可以随手拿来使用。
1.4 具体例子
1.4.1 场景
我们借用每日上班情景来说明这一模式
1.4.2 代码示例
原型建立:
package com.prototype.pojo;
/**
* 日常生活类
*
* @author
*
*/
public class DayLife implements Cloneable {
// 构造方法
public DayLife() {
System.out.println("-- 执行构造方法了! --");
}
// 起床
private String getUp;
// 坐公交
private String byBus;
// 下车,买早餐
private String getFood;
// 中午小憩
private String noon;
// 下午开始工作
private String afternoonWork;
// 下班回家
private String goHome;
// 晚上休闲
private String night;
public String getGetUp() {
return getUp;
}
public void setGetUp(String getUp) {
this.getUp = getUp;
}
public String getByBus() {
return byBus;
}
public void setByBus(String byBus) {
this.byBus = byBus;
}
public String getGetFood() {
return getFood;
}
public void setGetFood(String getFood) {
this.getFood = getFood;
}
public String getNoon() {
return noon;
}
public void setNoon(String noon) {
this.noon = noon;
}
public String getAfternoonWork() {
return afternoonWork;
}
public void setAfternoonWork(String afternoonWork) {
this.afternoonWork = afternoonWork;
}
public String getGoHome() {
return goHome;
}
public void setGoHome(String goHome) {
this.goHome = goHome;
}
public String getNight() {
return night;
}
public void setNight(String night) {
this.night = night;
}
/**
* 打印输出日常生活信息
*/
public void print() {
System.out.println(this.getGetUp());
System.out.println(this.getByBus());
System.out.println(this.getGetFood());
System.out.println(this.getNoon());
System.out.println(this.getAfternoonWork());
System.out.println(this.getGoHome());
System.out.println(this.getNight());
}
/**
* clone方法
*/
@Override
public DayLife clone() {
try {
// 调用超类的clone方法(超类?也没有集成任何类啊?哪里来的超类?别忘记了,所有类都是Object的子类哦!)
return (DayLife) super.clone();
} catch (Exception e) {
}
return null;
}
}
创建生成原型对象的抽象工厂:
package com.prototype.factory;
import com.prototype.pojo.DayLife;
/**
* 工厂类
*
* @author
*
*/
public interface ILifeFactory {
/**
* 生产DayLife对象
*
* @return
*/
public DayLife getNewInstance();
}
创建生成原型对象的具体工厂:
package com.prototype.factory.impl;
import com.prototype.factory.ILifeFactory;
import com.prototype.pojo.DayLife;
/**
* 工厂实现类
*
* @author
*
*/
public class LifeFactoryImpl implements ILifeFactory {
// DayLife对象实例用于初始化
private static DayLife dayLife = null;
/**
* getNewInstance方法实现:
*
* 首先判断dayLife是否为null:
* 如果是null,则使用new创建一个DayLife对象,同时设置初始内容,然后赋值给dayLife对象实例,然后返回;
* 如果不是null,则使用dayLift的clone方法产生一个新对象并复制给dayLife对象,然后返回
*/
@Override
public DayLife getNewInstance() {
// 判断dayLife是否为null
if (dayLife == null) {
// 如果为null
// 输出是使用new 产生的对象。注意:new这个只使用一次哦!
System.out.println(" new DayLife !");
// 设置dayLife内容
dayLife = new DayLife();
dayLife.setGetUp("7:00起床");
dayLife.setByBus("7:30坐公交车");
dayLife.setGetFood("8:30到公司附近的公交站下车,经过路旁的早餐车时会顺便买好早餐一起带到公司");
dayLife.setNoon("午餐在公司附近的小餐馆解决,然后在办公室的座椅上小憩一会");
dayLife.setAfternoonWork("13:30开始了下午的工作");
dayLife.setGoHome("17:30准时下班");
dayLife.setNight("晚上休闲娱乐");
} else {
// 如果不为null
// 输出是使用clone方法产生的对象
System.out.println(" clone DayLife !");
// 将clone对象赋值给dayLife ,返回
dayLife = dayLife.clone();
}
return dayLife;
}
}
每日工作情景展现:
package com;
import com.prototype.factory.ILifeFactory;
import com.prototype.factory.impl.LifeFactoryImpl;
import com.prototype.pojo.DayLife;
/**
* 主应用程序
*
* @author
*
*/
public class Client {
public static void main(String[] args) {
// 创建工厂
ILifeFactory lifeFactory = new LifeFactoryImpl();
// 打印输出DayLife默认内容
lifeFactory.getNewInstance().print();
// 再次获得DayLife,修改getUp内容后 输出内容
System.out.println("------------------------");
DayLife dayLife = lifeFactory.getNewInstance();
dayLife.setGetUp("早上赖床了!7::15才起床!");
dayLife.print();
// 再次获得DayLife,修改getUp内容后 输出内容
// System.out.println("------------------------");
// DayLife dayLife2 = lifeFactory.getNewInstance();
// dayLife2.setGetUp("早上赖床了!7::30才起床!");
// dayLife2.print();
}
}
运行结果:
new DayLife !
-- 执行构造方法了! --
7:00起床
7:30坐公交车
8:30到公司附近的公交站下车,经过路旁的早餐车时会顺便买好早餐一起带到公司
午餐在公司附近的小餐馆解决,然后在办公室的座椅上小憩一会
13:30开始了下午的工作
17:30准时下班
晚上休闲娱乐
------------------------
clone DayLife !
早上赖床了!7::15才起床!
7:30坐公交车
8:30到公司附近的公交站下车,经过路旁的早餐车时会顺便买好早餐一起带到公司
午餐在公司附近的小餐馆解决,然后在办公室的座椅上小憩一会
13:30开始了下午的工作
17:30准时下班
晚上休闲娱乐
2、原型模式在源码中的应用
2.1 JDK源码中原型模式
我们先看JDK 中的 Cloneable 接口。
public interface Cloneable {
}
我们只需要在源码中看哪些类实现了 Cloneable 接口即可。下面是 ArrayList 类的实现代码。
public Object clone() {
try {
@SuppressWarnings("unchecked")
ArrayList<E> v = (ArrayList<E>) super.clone();
v.elementData = Arrays.copyOf(elementData, size);
v.modCount = 0;
return v;
} catch (CloneNotSupportedException e) {
// this shouldn't happen, since we are Cloneable
throw new InternalError();
}
}
从以上代码可以看出,clone() 方法只是将 List 中的元素循环遍历了一遍。此时,再思考一下,是不是这种形式就是深克隆呢?
下面用代码验证一下,继续修改 ConcretePrototype 类,增加一个 deepCloneHobbies() 方法,代码如下:
public class ConcretePrototype implements Cloneable,Serializable {
public ConcretePrototype deepCloneHobbies(){
try {
ConcretePrototype result = (ConcretePrototype) super.clone();
result.hobbies = (List) ((ArrayList) result.hobbies).clone();
return result;
}catch (CloneNotSupportedException){
return null;
}
}
...
}
客户端代码修改如下:
public static void main(String[] args) {
...
//复制原型对象
ConcretePrototype cloneType = prototype.deepCloneHobbies();
...
}
运行结果如下:
原型对象:ConcretePrototype{age=18,name='C语言中文网',hobbies=[书法, 美术]}
克隆对象:ConcretePrototype{age=18,name='C语言中文网',hobbies=[书法, 美术, 技术控]}
可以发现以上结果与《使用序列化实现深克隆》一节运行结果相同,说明以上形式是深克隆。但是这样的代码是硬编码。如果在对象中声明了各种集合类型,则每种情况都需要单独处理。因此,深克隆的写法一般会直接用序列化来操作。
因此,总结如下,如果需要深克隆,如何编写Java代码呢:
- 1、自己new对象。在自己的clone方法里面,该创建就创建,该赋值就赋值。
- 2、巧妙地利用序列化进行复制。
- 3、在clone中复制成员对象的时候也调用clone,进行层层复制。
2.2 Spring源码中原型模式
spring 提供了5种scope分别是singleton、 prototype、 request、 session、global session。
在上文中单例模式中我们讲到,spring bean默认是单例模式,当设置为prototype模式时,对于原型(prototype)bean来说当每次请求来的时候直接实例化新的bean,没有缓存以及从缓存查的过程。
1 创建ioc的时候创建了一个这样的容器实例
public Object getBean(String name) throws BeansException {
this.assertBeanFactoryActive();
//我们先进入getBeanFactory()方法,看看得到的是哪个BeanFactory,再寻找他的getBean()方法
return this.getBeanFactory().getBean(name);
}
//追踪发现这是一个抽象方法,应该是由AbstractApplicationContext的子类实现
public abstract ConfigurableListableBeanFactory getBeanFactory() throws IllegalStateException;
2 往下找getBeanFactory()方法的实现
3 发现在AbstractRefreshableApplicationContext中实现了这个方法,返回的是一个DefaultListableBeanFactory,也就是调用了DefaultListableBeanFactory的getBean()方法
private DefaultListableBeanFactory beanFactory;
//实现了getBeanFactory方法,确保了线程安全的前提下返回了一个DefaultListableBeanFactory
public final ConfigurableListableBeanFactory getBeanFactory() {
synchronized(this.beanFactoryMonitor) {
if (this.beanFactory == null) {
throw new IllegalStateException("BeanFactory not initialized or already closed - call 'refresh' before accessing beans via the ApplicationContext");
} else {
return this.beanFactory;
}
}
}
4 去到DefaultListableBeanFactory中没有找到getBean()方法,于是往他的父类去找
5 从这个bean的父类的父类AbstractBeanFactory可以看到这个getBean(),同时调用了核心方法doGetBean();
public Object getBean(String name) throws BeansException {
return this.doGetBean(name, (Class)null, (Object[])null, false);
}
6 进入到doGetBean()方法可以发现,spring对参数进行了判断,对应调用createBean创建了原型模式的对象
protected <T> T doGetBean(String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly) throws BeansException {
String beanName = this.transformedBeanName(name);
Object sharedInstance = this.getSingleton(beanName);
.................
if (mbd.isSingleton()) {
sharedInstance = this.getSingleton(beanName, () -> {
try {
return this.createBean(beanName, mbd, args);
} catch (BeansException var5) {
this.destroySingleton(beanName);
throw var5;
}
});
bean = this.getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
//判断了是否设置了原型模式
} else if (mbd.isPrototype()) {
var11 = null;
Object prototypeInstance;
try {
this.beforePrototypeCreation(beanName);
//进入了原型模式的对象创建
prototypeInstance = this.createBean(beanName, mbd, args);
} finally {
this.afterPrototypeCreation(beanName);
}
bean = this.getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
} else {
...................
3、 深拷贝的实现方式
//深拷贝:方式一,通过重写clone方法来实现深拷贝,不推荐
//方式二:通过对象的序列化实现
public Object deepClone() {
//创建流
ByteArrayOutputStream bos = null;
ObjectOutputStream oos = null;
ByteArrayInputStream bis =null;
ObjectInputStream ois = null;
try {
//序列化
bos = new ByteArrayOutputStream();
oos = new ObjectOutputStream(bos);
oos.writeObject(this);//当前的对象以流的方式输出
//反序列化
bis = new ByteArrayInputStream(bos.toByteArray());
ois = new ObjectInputStream(bis);
DeepPrototype deepPrototype = (DeepPrototype)ois.readObject();
return deepPrototype;
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
return null;
}finally {
try {
bos.close();
oos.close();
bis.close();
ois.close();
} catch (Exception e2) {
// TODO: handle exception
}
}
}
如上,深拷贝方式1:重写clone方法,对象里属性也clone,以此来达到深拷贝。深拷贝方式2:如上,使用序列化方式。个人觉得简单一些,转json也能实现类似效果,是否可以作为第三种方式?
4、原型模式优缺点
4.1 优点
- (1)原型模式是在内存中二进制流的拷贝,要比直接new一个对象性能好很多,特别是要在一个循环体内产生大量对象时,原型模式可能更好的体现其优点。
- (2)还有一个重要的用途就是保护性拷贝,也就是对某个对象对外可能是只读的,为了防止外部对这个只读对象的修改,通常可以通过返回一个对象拷贝的形式实现只读的限制。
4.2 缺点
- (1)这既是它的优点也是缺点,直接在内存中拷贝,构造函数是不会执行的,在实际开发中应该注意这个潜在问题。优点是减少了约束,缺点也是减少了约束,需要大家在实际应用时考虑。
- (2)通过实现Cloneable接口的原型模式在调用clone函数构造实例时并不一定比通过new操作速度快,只有当通过new构造对象较为耗时或者说成本较高时,通过clone方法才能够获得效率上的提升。
- (3)**缺陷:**需要为每一个类配备一个克隆方法,对于已经存在的类来说得修改源代码,这违背了ocp原则
4.3 注意
构造函数不会被执行,需格外注意。
参考文章:
https://blog.csdn.net/chengqiuming/article/details/70139285
https://www.runoob.com/design-pattern/prototype-pattern.html
http://c.biancheng.net/view/8383.html
https://blog.csdn.net/qq_27007251/article/details/71773720
https://blog.csdn.net/Y_Mlsy/article/details/107282359
https://blog.csdn.net/weixin_44198475/article/details/106594811