👈️上一篇:建造者模式 | 下一篇:创建型设计模式对比👉️
目录
- 原型模式(Prototype Pattern)
- 概览
- 定义
- 英文原话
- 直译
- 3个角色
- 类图
- 1. 抽象原型(Prototype)角色
- 2. 具体原型(Concrete Prototype)角色
- 3. 客户(Client)角色
- 代码示例
- 1. 抽象原型
- 2. 具体原型
- 3. 被复制的对象的类
- 4. 客户端
- 5. 测试类
- 应用
- 优点
- 使用场景
- 原型模式示例解析:邮件群发
- 类图
- 1. 抽象原型角色:Prototype.java(接口定义克隆方法)
- 2. 具体原型类:Mail.java
- 3. 测试类
- 补充知识点
- Lombok的@Builder注解原理:建造者模式
- 1. 先说下用法
- 2. 原理分析
原型模式(Prototype Pattern)
>>本文源码<<
概览
- 定义
- 3个角色
- 代码示例
- 应用
- 优点
- 使用场景
- 案例分析
- 原型模式示例解析:邮件群发
定义
英文原话
Specify the kinds of objects to create using a prototypical instance, and create new objects by copying this prototype.
直译
指定要使用原型实例创建的对象类型,并通过复制该原型创建新对象。
3个角色
类图
1. 抽象原型(Prototype)角色
为具体原型角色定义方法,指定统一标准
2. 具体原型(Concrete Prototype)角色
该角色是被复制的对象,必须实现抽象原型接口。
Java中内置了克隆机制,Object类具有一个clone()方法,能够实现对象的克隆,使一个类支持克隆需要以下两步。
- 实现Cloneable接口;
- 覆盖Object的clone()方法,完成对象的克隆操作,通常只需要调用Object的clone()方法(“浅克隆”-即只复制关联对象的引用)即可
3. 客户(Client)角色
该角色提出创建对象的请求。
代码示例
>>示例源码<<
1. 抽象原型
抽象原型Prototype接口继承 Cloneable 接口,以标明该接口的实现类可以被复制,并声明一个 clone()方法,该clone()方法是对Object类的clone()方法的重写
package com.polaris.designpattern.list1.creational.pattern5.prototype.proto;
/** 实现本接口,表明实现类可以被复制*/
public interface Prototype extends Cloneable {
// 克隆方法
Prototype clone();
}
2. 具体原型
具体原型ConcretePrototype实现clone()
方法。这里调用的父类Object的clone()
方法
package com.polaris.designpattern.list1.creational.pattern5.prototype.proto;
/** 具体原型ConcretePrototype实现clone()方法*/
public class ConcretePrototype implements Prototype {
@Override
public Prototype clone() {
try {
// 此处调用的Object类的clone()方法,并向下转型为抽象原型类型。
// Java中Object提供的clone()方法采用的是“浅”克隆,即只复制关联对象的引用;
// 而不复制关联对象的数据。如果需要“深”克隆,则需要在覆盖clone()方法时手动控制克隆的深度。
return (Prototype) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
return null;
}
}
}
3. 被复制的对象的类
提供一个被复制的类,用于测试使用
package com.polaris.designpattern.list1.creational.pattern5.prototype.proto;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
/** 被复制的对象的类*/
@Data
@Builder
@EqualsAndHashCode(callSuper = true)
public class User extends ConcretePrototype {
private String name;
private Integer age;
}
这里的@Builder是lombok的注解,加上该注解编译出的class文件中,通过内部类的方式实现了建造者模式。便于用户方便地创建对象。
(关于建造者模式看上一篇 建造者模式)
@Builder的原理,参考补充知识点:Lombok的@Builder注解原理:建造者模式
4. 客户端
使用原型类的客户类
public class Client {
// 传参传入具体原型类示例,具体原型类实现了抽象原型的clone方法
public Prototype operation(Prototype example) {
// 得到example的副本
Prototype p = example.clone();
return p;
}
}
5. 测试类
新建了一个用于被复制的
user
对象,调用客户类进行复制,传入原型类型的对象(User类实现了原型类,通过继承具体原型类实现的),然后返回一个原型实例,
之后测试了复制对象与原始对象属性是同一对象,
然后对复制对象的属性值通过反射进行了获取与打印
package com.polaris.designpattern.list1.creational.pattern5.prototype.proto;
import java.lang.reflect.Field;
/**
* @author Polaris 2024/5/17
*/
public class DemoTest {
public static void main(String[] args) {
User user = User.builder().name("历史").age(5000).build();
Client client = new Client();
Prototype clone = client.operation(user);
System.out.println("name属性是同一个:"+((User)clone).getName().equals(user.getName()));
System.out.println("age属性(>127,非读缓存值)是同一个:"+((User)clone).getAge().equals(user.getAge()));
System.out.println("-----------------");
Field[] declaredFields = clone.getClass().getDeclaredFields();
for (Field declaredField : declaredFields) {
Object o;
try {
String name = declaredField.getName();
declaredField.setAccessible(true);
o = declaredField.get(clone);
System.out.println(name + ":" + o);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
/* Output:
name属性是同一个:true
age属性(>127,非读缓存值)是同一个:true
-----------------
name:历史
age:5000
*///~
输出中:克隆前后的对象的属性指向的是同一个对象。
也印证了:
Java中Object
提供的clone()
方法采用的是“浅”克隆,即只复制关联对象的引用
应用
优点
原型模式的优点有以下几个方面。
- 性能优良:原型模式是在内存二进制流的复制,要比直接new一个对象性能好,特别是在一个循环体内产生大量的对象时,原型模式可以更好地体现其优点。
- 逃避构造函数的约束:这既 是优点也是缺点,直接在内存中复制,构造函数是不会执行的,因此减少了约束,需要在实际应用时进行权衡考虑。
使用场景
原型模式的使用场景如下。
- 资源优化场景,类初始化需要消化非常多的资源,这个资源包括数据、硬件资源等。
- 性能和安全要求的场景,通过new产生一个对象需要非常烦琐的数据准备或访问权限,可以使用原型模式。
- 一个对象多个修改者的场景,一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时,可以考虑使用原型模式复制多个对象供调用者使用。
- [注]:在实际项目中,原型模式很少单独出现,一般是和工厂方法模式一起出现,通过clone()方法创建一个对象,然后由工厂方法提供给调用者。
- 注意Java中Object提供的clone()方法采用的是“浅”克隆,即只复制关联对象的引用,而不复制关联对象的数据。如果需要“深”克隆,则需要在覆盖clone()方法时手动控制克隆的深度。
原型模式示例解析:邮件群发
>>示例源码<<<
本案例是一个对象多个修改者情况:
- 每次发送邮件都是对原始邮件对象克隆然后进行属性修改
- 原始邮件对象实现了抽象原型接口
- 抽象原型接口继承Cloneable接口,声明提供抽象原型类型的clone方法声明
- 原始邮件类型实现抽象原型类型的
clone()
方法,用于克隆对象 - 使用Object的克隆方法来克隆邮件类型对象,浅克隆,复制邮件类对象的引用
类图
1. 抽象原型角色:Prototype.java(接口定义克隆方法)
package com.polaris.designpattern.list1.creational.pattern5.prototype;
public interface Prototype extends Cloneable {
//克隆方法
Prototype clone();
}
2. 具体原型类:Mail.java
创建一个邮件类Mail:
Mail类实现Cloneable接口,并实现了
clone()
方法,该方法是实现原型模式的关键,只有实现
clone()
方法,在应用中才能对Mail进行复制克隆
package com.polaris.designpattern.list1.creational.pattern5.prototype;
import lombok.Getter;
import lombok.Setter;
public class Mail implements Prototype {
//收件人
@Getter
@Setter
private String recerver;
//邮件标题
@Getter
@Setter
private String subject;
//称谓
@Getter
@Setter
private String appellation;
//邮件内容
@Getter
@Setter
private String contxt;
//邮件尾部,一般是加上“xxx版权所有”等信息
@Getter
@Setter
private String tail;
//构造函数
public Mail(String subject, String contxt) {
this.subject = subject;
this.contxt = contxt;
}
//克隆方法
@Override
public Prototype clone() {
Mail mail = null;
try {
mail = (Mail) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return mail;
}
}
3. 测试类
通过邮件的克隆群发演示原型模式
package com.polaris.designpattern.list1.creational.pattern5.prototype;
import java.util.Random;
public class DemoTest {
//发送账单的数量,这个值从数据库中获得的
private static int MAX_COUNT = 6;
public static void main(String[] args) {
//模拟发送邮件
int i = 0;
//定义一个邮件对象
Prototype prototype = new Mail("某商场五一抽奖活动",
"五一抽奖活动通知:" +
"凡在五一期间在本商场购物满100元的客户都有货的抽奖机会!...");
((Mail)prototype).setTail("解释权归某商场所有");
while (i < MAX_COUNT) {
//克隆邮件
Mail cloneMail = (Mail) prototype.clone();
//以下是每封邮件不同的地方
cloneMail.setAppellation(getRandomString(5) + " 先生(女士)");
cloneMail.setRecerver(getRandomString(5) + "@" + getRandomString(8) + ".com");
//发送邮件
sendMail(cloneMail);
i++;
}
}
//发送邮件
public static void sendMail(Mail mail) {
System.out.println("标题:" + mail.getSubject() +
"\t收件人:" + mail.getRecerver() + "\t...发送成功!");
}
//获取指定长度的随机字符串
public static String getRandomString(int maxLength) {
String souce = "abcdefghijklmnopqrstuvwxyz" +
"ABCDEFGHIJKLMNOPQRSTUVWXYZ";
StringBuilder sb = new StringBuilder();
Random random = new Random();
for (int i = 0; i < maxLength; i++) {
sb.append(souce.charAt(random.nextInt(souce.length())));
}
return sb.toString();
}
}
/* Output:
标题:某商场五一抽奖活动 收件人:TEDiM@LXFeyNdU.com ...发送成功!
标题:某商场五一抽奖活动 收件人:qyOWv@wieXPRga.com ...发送成功!
标题:某商场五一抽奖活动 收件人:wMloC@IgFOzjBh.com ...发送成功!
标题:某商场五一抽奖活动 收件人:hrDGv@HAjpARpN.com ...发送成功!
标题:某商场五一抽奖活动 收件人:lyWwt@cLdWntpC.com ...发送成功!
标题:某商场五一抽奖活动 收件人:ZVeap@HZlYxaCe.com ...发送成功!
*///~
补充知识点
Lombok的@Builder注解原理:建造者模式
1. 先说下用法
通过下面的链式调用方式,可以非常方便的得到一个需要的user对像
User user = User.builder().name("历史").age(5000).build();
2. 原理分析
以下是标记了@Builder注解后,编译出的类文件中看到增加了以下代码:
public class User extends ConcretePrototype {
private String name;
private Integer age;
User(String name, Integer age) {
this.name = name;
this.age = age;
}
public static User.UserBuilder builder() {
return new User.UserBuilder();
}
public static class UserBuilder {
private String name;
private Integer age;
UserBuilder() {
}
public User.UserBuilder name(String name) {
this.name = name;
return this;
}
public User.UserBuilder age(Integer age) {
this.age = age;
return this;
}
public User build() {
return new User(this.name, this.age);
}
public String toString() {
return "User.UserBuilder(name=" + this.name + ", age=" + this.age + ")";
}
}
}
通过标记了@Builder注解的类调用类方法
builder()
方法,这里创建了一个内部类UserBuilder
对象接下来就是构造者模式的实现:
内部类
UserBuilder
就是建造者角色,User类的内部类
UserBuilder
的属性和User
类的属性完全一样,然后提供了对这些属性配置的方法,且每个方法都会将该实例返回,这样就可以进行链式调用。最后提供了一个
build()
方法,将内部类的属性赋值给User类的构造函数,来构造User类实例,返回给客户端。这就是建造者模式的具体应用。
👈️上一篇:建造者模式 | 下一篇:创建型设计模式对比👉️