原型模式属于创建型设计模式。通过复制现有的实例来创建新的实例,无需知道相应类的信息。
简单的讲就是当我需要创建一个指定的对象时,刚好现在就有这个对象,但又不能直接使用,所以简单的方式就是克隆一个一摸一样的对象来使用。
- 抽象原型类(Prototype):定义了一个抽象的克隆方法。
- 具体原型类(ConcretePrototyoe):实现抽象原型类(接口)定义的克隆方法,提供一个具体的克隆方法来复制自己。
- 客户端(Client):使用原型类的对象来实现具体的操作,即通过复制原型对象来创建新的对象。
优点:
- 提高了对象创建的效率,在创建大量对象时可以节省时间和资源;
- 可以隐藏对象创建和初始化的复杂性,并且更容易管理和维护;
- 可以在运行时动态添加和删除对象;
- 可以保护原始对象,防止意外修改对原对象产生影响。
缺点:
- 必须保证原始对象和克隆对象之间的区别,否则可能会产生副作用;
- 有些对象可能无法进行有效地复制,例如涉及到与其他外部对象交互的对象;
- 原型模式需要给对象添加一个克隆方法。但是,该方法可能不适用于所有对象类型,例如具有命令行参数的程序。
- 克隆羊多利
- 细胞分裂
- 孙悟空的七十二变
- Object类:Java中的所有类都直接或间接继承自Object类,它提供了一个clone()方法,允许对象在克隆时使用它们的原型对象。
- Collection框架:Iterator接口使用原型模式来提供多个访问数据的独立副本(例如ListIterator和Enumeration)。这种方式可以确保迭代器始终指向正确的位置。
- Apache Commons BeanUtils:Apache Commons BeanUtils库采用了原型模式的方法,通过使用BeanUtils.cloneBean()方法来创建新对象并通过复制其属性来克隆一个Bean。
- Spring框架:在Spring框架中,原型范围bean使用原型模式。例如,在Spring中,可以将作用域设置为prototype,来创建一个bean的多个独立实例,这样每次在容器中注入bean时,将创建新的实例。
下面以英雄联盟塞拉斯窃取其他英雄大招为例。
定义一个具体原型类(HeroSkill),也就是被窃取技能的英雄,实现了Cloneable接口(抽象原型类)。
/**
* @author Created by njy on 2023/6/7
* 具体的原型类,被窃取技能的英雄
*/
public class HeroSkill implements Cloneable{
private String name;
private String bigMove;
public HeroSkill(){
}
public HeroSkill(String name, String bigMove){
this.name=name;
this.bigMove=bigMove;
}
@Override
public HeroSkill clone() {
HeroSkill clone= null;
try {
clone = (HeroSkill) super.clone();
} catch (CloneNotSupportedException e) {
throw new RuntimeException(e);
}
System.out.println("塞拉斯窃取"+name+"的大招:"+bigMove);
return clone;
}
//英雄的大招展示
public void showSkill() {
System.out.println(name+"的大招:"+bigMove);
}
}
塞拉斯工厂客户端,用于复制对象(英雄)
/**
* @author Created by njy on 2023/6/7
* 工厂类,用于在客户端中复制对象
* (塞拉斯,窃取别的英雄技能)
*/
public class StealManFactory {
private HeroSkill heroSkill;
public StealManFactory(HeroSkill heroSkill){
this.heroSkill=heroSkill;
}
public void setHeroSkill(HeroSkill heroSkill){
this.heroSkill=heroSkill;
}
public HeroSkill cloneHeroSkill(){
return heroSkill.clone();
}
}
/**
* @author Created by njy on 2023/6/7
* 原型模式
*/
@SpringBootTest
public class TestPrototype {
@Test
void testPrototype(){
//初始化英雄
HeroSkill heroSkill=new HeroSkill("盲僧","神龙摆尾");
//初始化工厂类(塞拉斯)
StealManFactory factory=new StealManFactory(heroSkill);
//复制英雄技能
HeroSkill cloneHeroSkill = factory.cloneHeroSkill();
//塞拉斯复制的技能
cloneHeroSkill.showSkill();
System.out.println("-------下方原英雄技能展示----------");
//原英雄技能
heroSkill.showSkill();
}
}
适用于开发的场景:
- 如果一个对象的创建过程包括繁琐的准备工作或重量级的资源初始化,那么每次需要创建新对象时,都需要必须执行这些初始操作,这时就可以使用原型模式,通过复制旧对象来创建新对象,从而避免创建成本高的问题。
- 如果对象需要修改的属性较多,使用原型模式则可以在原始对象的基础上进行修改,减少代码量。
- 如果存在多个对象需要共享同一个数据源,可以使用原型模式基于已有的原始对象来进行克隆,避免了重复创建多个对象。
- 当对象的创建过程涉及多个线程时,需要注意线程安全性。原型模式可以用于在不同的线程之间共享原型对象,并在每个线程中创建对象的副本,确保线程安全性。
在实际项目中,原型模式很少单独出现,一般是和工厂方法模式一起出现,通过 clone 的方法创建一个对象,然后由工厂方法提供给调用者。
END:更多设计模式的介绍,推荐移步至 23种设计模式学习导航(Java完整版)