创建型设计模式(5种)
创建型设计模式(5种)
创建型设计模式(5种)
概述
创建型设计模式主要解决对象的创建问题,封装复杂的创建过程,将对象的创建和使用解耦。
分类
创建型(5种): 提供创建对象的机制,提升已有代码的灵活性和复用性
- 常用: 单例模式 、工厂模式(工厂方法、抽象工厂)、建造者模式。
- 不常用: 原型模式
1、单例模式
1.1、定义
某个类在运行期间,只有一个实例对外服务。(一个类只有一个实例)。
1.2、使用单例模式要做的2件事
- 保证一个类只有一个实例。
- 为这个实例提供对外的全局访问方法。
1.3、实现
1.3.1、饿汉式
特点: 不支持延迟加载,获取实例对象的速度比较快,但是如果对象比较大,而且一直没使用会造成内存的浪费。
饿汉式: 饿汉子饥不择食,所以在类实例化时就会实例化一次。
- 由于静态变量只会实例化一次,所以这样创建是单例的。
public class Singleton_01 {
// 1、在类中创建私有静态的全局对象
private static Singleton_01 instance = new Singleton_01();
// 2、私有化构造方法
private Singleton_01() {
}
// 3、提供一个全局访问点,供外部获取对象
public static Singleton_01 getInstance() {
return instance;
}
}
- 测试代码
public class Test01 {
@Test
public void test01(){
Singleton_01 instance01 = Singleton_01.getInstance();
Singleton_01 instance02 = Singleton_01.getInstance();
Singleton_01 instance03 = Singleton_01.getInstance();
System.out.println(instance01 == instance02);
System.out.println(instance02 == instance03);
System.out.println(instance01 == instance03);
}
}
- 测试结果
1.3.2、懒汉式
特点: 支持懒加载,只有调用 getInstance的时候才会创建对象。
懒汉式: 懒惰的汉子不想动,你叫我动时我才动,所以会在用到的时候才实例化。
1.3.2.1 懒汉式—— (线程不安全): 如果按照以下方式实例化,那么在高并发下,就会创建多实例。
public class Singleton_02 {
// 1、在类中创建私有静态的全局对象
private static Singleton_02 instance;
// 2、私有化构造方法
private Singleton_02() {
}
// 3、提供一个全局访问点,供外部获取对象
public static Singleton_02 getInstance() {
if (instance == null) {
// 有可能还没实例化时就有多个线程走到这行注释这边
// 一个实例化了之后,还有其他几个线程继续实例化,就会产生多实例
instance = new Singleton_02();
}
return instance;
}
}
- 测试代码
public class Test01 {
@Test
public void test02(){
for (int i = 0; i < 1000; i++) {
new Thread(()->{
System.out.println(Thread.currentThread().getName() + "-------->" + Singleton_02.getInstance());
}).start();
}
}
}
- 测试结果 (创建了多实例,线程不安全)
1.3.2.2 懒汉式——线程安全 (但由于对getInstance方法加了synchronized同步锁,导致并发量很低)
public class Singleton_03 {
// 1、在类中创建私有静态的全局对象
private static Singleton_03 instance;
// 2、私有化构造方法
private Singleton_03() {
}
// 3、提供一个全局访问点,供外部获取对象
public static synchronized Singleton_03 getInstance() {
if (instance == null) {
instance = new Singleton_03();
}
return instance;
}
}
- 测试代码
package com.cl.test;
import com.cl.singleton.demo.Singleton_01;
import com.cl.singleton.demo.Singleton_02;
import com.cl.singleton.demo.Singleton_03;
import org.junit.jupiter.api.Test;
/**
* @author chenglongh
* @version 2022/12/6 17:15
*/
public class Test01 {
@Test
public void test03(){
for (int i = 0; i < 1000; i++) {
new Thread(()->{
System.out.println(Thread.currentThread().getName() + "-------->" + Singleton_03.getInstance());
}).start();
}
}
}
- 测试结果 (单例)
1.3.2.3 懒汉式——双重校验 (提升并发量)
public class Singleton_04 {
// 1、在类中创建私有静态的全局对象
private static Singleton_04 instance;
// 2、私有化构造方法
private Singleton_04() {
}
// 3、提供一个全局访问点,供外部获取对象
public static Singleton_04 getInstance() {
// 第一次判断,如果不为 null,不进入抢锁阶段,直接返回实例
if (instance == null) {
synchronized (Singleton_04.class) {
// 第二次判断,抢到锁之后再次判断,判断是否为 null
if (instance == null) {
instance = new Singleton_04();
}
}
}
return instance;
}
}
1.3.2.4 懒汉式——双重校验 (加上volatile,屏蔽JVM指令重排)
public class Singleton_04 {
// 1、在类中创建私有静态的全局对象
// volatile保证变量的可见性(1、一个线程修改时,其他线程能立即得到修改后的结果 2、屏蔽指令重排序)
private volatile static Singleton_04 instance;
// 2、私有化构造方法
private Singleton_04() {
}
// 3、提供一个全局访问点,供外部获取对象
public static Singleton_04 getInstance() {
// 第一次判断,如果不为 null,不进入抢锁阶段,直接返回实例
if (instance == null) {
synchronized (Singleton_04.class) {
// 第二次判断,抢到锁之后再次判断,判断是否为 null
if (instance == null) {
instance = new Singleton_04();
}
}
}
return instance;
}
}
1.3.2.5 懒汉式——静态内部类 ( 在静态内部类中创建单例,在装载内部类的时候,才会创建单例对象)
public class Singleton_05 {
private Singleton_05(){
}
// 创建静态内部类
private static class SingletonHandler{
// 在静态内部类中创建单例,在装载内部类的时候,才会创建单例对象
private static Singleton_05 instance = new Singleton_05();
}
public static Singleton_05 getInstance(){
return SingletonHandler.instance;
}
}
1.3.2.6 懒汉式——额外知识点:阻止反射的破坏 (以静态内部类为例)
public class Singleton_05 implements Serializable {
private Singleton_05(){
// 构造方法内部屏蔽反射创建对象
// 这样破坏了单例的简洁性,不够优雅
if(SingletonHandler.instance != null){
throw new RuntimeException("单例模式,只允许存在一个对象");
}
}
// 创建静态内部类
private static class SingletonHandler{
// 在静态内部类中创建单例,在装载内部类的时候,才会创建单例对象
private static Singleton_05 instance = new Singleton_05();
}
public static Singleton_05 getInstance(){
return SingletonHandler.instance;
}
}
1.3.2.7 懒汉式——额外知识点:阻止序列化的破坏 (以静态内部类为例)
ObjectInputStream中的readObject方法中的readObject0方法中的readOrdinaryObject中的desc.hasReadResolveMethod()判断是否有readResolve方法,如果有就用有的,没有则用newInstance。
public class Singleton_05 implements Serializable {
private Singleton_05(){
// 这样破坏了单例的简洁性,不够优雅
if(SingletonHandler.instance != null){
throw new RuntimeException("单例模式,只允许存在一个对象");
}
}
// 创建静态内部类
private static class SingletonHandler{
// 在静态内部类中创建单例,在装载内部类的时候,才会创建单例对象
private static Singleton_05 instance = new Singleton_05();
}
public static Singleton_05 getInstance(){
return SingletonHandler.instance;
}
/**
* 只需要在单例类中定义readResolve,就可以解决序列化对于单例的破坏
* 程序会判断是否有 readResolve方法,如果有就执行,反之就会 newInstance一个新的对象
*/
public Object readResolve(){
return SingletonHandler.instance;
}
}
1.3.3、枚举单例 (推荐使用)
-
阻止了反射的破坏 (因为枚举无构造方法,同时newInstance方法里也判断了属于枚举类则抛出异常:Cannot reflectively create enum objects)
-
阻止了序列化的破坏 (每个枚举类型定义的枚举变量都是唯一的,在序列化的时候仅仅是将枚举对象的 name 属性输出到结果中, 反序列化的时候就会通过 Enum 的 valueOf 方法根据名字去查找对应的枚举对象)
public enum Singleton_06 {
INSTANCE;
public static Singleton_06 getInstance() {
return Singleton_06.INSTANCE;
}
}
1.3.4、单例模式总结
- 饿汉式
饿汉式的实现方式,在类加载的期间,就已经将 instance 静态实例初始化好了,所以 instance 实例的创建是线程安全的。不过,这样的实现方式不支持延迟加载实例。
- 懒汉式
相对于饿汉式的优势是支持延迟加载。这种实现方式会导致频繁加锁、释放锁,以及并发度低等问题,频繁的调用会产生性能瓶颈。
- 双重检测
双重检测实现方式既支持延迟加载、又支持高并发的单例实现方式。只要 instance 被创建之后,再调用 getInstance() 函数都不会进入到加锁逻辑中。所以,这种实现方式解决了懒汉式并发度低的问题。
- 静态内部类
利用 Java 的静态内部类来实现单例。这种实现方式,既支持延迟加载,也支持高并发,实现起来也比双重检测简单。
- 枚举方式
最简单的实现方式,基于枚举类型的单例实现。这种实现方式通过 Java 枚举类型本身的特性,保证了实例创建的线程安全性和实例的唯一性(同时阻止了反射和序列化对单例的破坏)。
2、工厂模式
2.1、定义
工厂方法使一个产品类的实例延迟到其工厂的子类(定义一个接口,让子类决定实例化哪个接口的实现类)。
2.2、目的
封装对象的创建过程,提升创建对象的可复用性。
2.3、主要角色
-
抽象工厂:提供创建产品的接口,调用者通过访问接口访问具体工厂的工厂方法来创建产品 。
-
具体工厂:实现抽象工厂的抽象方法,完成具体产品的创建。
-
抽象产品:定义了产品的规范,描述了产品的主要特性和功能。
-
具体产品 :实现了抽象产品所定义的接口,由具体工厂创建,它与具体工厂之间一一对应。
2.4、特点
一个具体工厂只生产一个具体产品
2.5、UML图
USBFactory (抽象工厂):用来创建 USB 接口的工厂。
TypeAFactoryImpl (具体工厂):具体实现 USBFactory,完成Type-A类型USB产品的实现,专门用来生产Type-A对象。
TypeCFactoryImpl (具体工厂):具体实现 USBFactory,完成Type-C类型USB产品的实现,专门用来生产Type-C对象。
IUSBGoods (抽象产品):抽象的 USB 产品,描述了 USB 产品传送数据的功能。
USBGoods1Impl (具体产品):具体的 USB 产品1(实现了抽象的 USB 接口),具体描述了产品1的传送数据功能。
USBGoods2Impl (具体产品):具体的 USB 产品2(实现了抽象的USB接口),具体描述了产品2的传送数据功能。
2.6、实现
USBGoods (抽象产品):抽象出产品主要的特性
public abstract class USBGoods {
private Integer type;
private String name;
private String uid;
public abstract void sendData();
public Integer getType() {
return type;
}
public void setType(Integer type) {
this.type = type;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getUid() {
return uid;
}
public void setUid(String uid) {
this.uid = uid;
}
}
USBGoods1Impl (具体产品):具体的USB产品1(实现了抽象的 USB 接口),具体描述了产品1的传送数据功能。
public class USBGoods1Impl extends USBGoods {
@Override
public void sendData() {
System.out.println("USB产品1(USBGoods1Impl)发送数据");
}
}
USBGoods2Impl (具体产品):具体的 USB 产品2(实现了抽象的 USB 接口),具体描述了产品2的传送数据功能。
public class USBGoods2Impl extends USBGoods {
@Override
public void sendData() {
System.out.println("USB产品2(USBGoods2Impl)发送数据");
}
}
USBFactory (抽象工厂):用来创建 USB 接口的工厂。
public interface USBFactory {
USBGoods getInstance();
}
TypeAFactoryImpl (具体工厂):具体实现 USBFactory,完成Type-A类型USB产品的实现,专门用来生产Type-A对象。
public class TypeAUSBFactoryImpl implements USBFactory{
@Override
public USBGoods getInstance() {
return new USBGoods1Impl();
}
}
TypeCFactoryImpl (具体工厂):具体实现 USBFactory,完成Type-C类型USB产品的实现,专门用来生产Type-C对象。
public class TypeCUSBFactoryImpl implements USBFactory{
@Override
public USBGoods getInstance() {
return new USBGoods2Impl();
}
}
USBGoodsFactoryMap:专供对外使用的工厂容器。
public class USBGoodsFactoryMap{
private static Map<Integer, USBFactory> caches = new HashMap<>();
static {
caches.put(1, new TypeAUSBFactoryImpl());
caches.put(2, new TypeCUSBFactoryImpl());
}
public static USBFactory getInstance(Integer key){
return caches.get(key);
}
}
2.7、测试代码
public class TestFactory {
@Test
public void testExample01(){
USBFactory usbFactory = USBGoodsFactoryMap.getInstance(1);
USBGoods goods = usbFactory.getInstance();
goods.sendData();
}
}
2.8、测试效果
2.9、总结
工厂方法模优缺点
优点:
- 用户只需要知道具体工厂的名称就可得到所要的产品,无须知道产品的具体创建过程;
- 在系统增加新的产品时只需要添加具体产品类和对应的具体工厂类,无须对原工厂进行任何修改,满足开闭原则;
缺点:
- 每增加一个产品就要增加一个具体产品类和一个对应的具体工厂类,这增加了系统的复杂度。
应用场景
- 需要使用很多重复代码创建对象时,比如,DAO 层的数据对象、API 层的 VO 对象等。
- 创建对象要访问外部信息或资源时,比如,读取数据库字段,获取访问授权 token 信息,配置文件等。
- 创建需要统一管理生命周期的对象时,比如,会话信息、用户网页浏览轨迹对象等。
- 创建池化对象时,比如,连接池对象、线程池对象、日志对象等。这些对象的特性是:有限、可重用,使用工厂方法模式可以有效节约资源。
- 希望隐藏对象的真实类型时,比如,不希望使用者知道对象的真实构造函数参数等。
3、抽象工厂模式
3.1、定义
提供一个生产一系列相关或相互依赖对象的接口,而无须指定它们具体的类。
抽象工厂模式比工厂方法模式的抽象程度更高。在工厂方法模式中每一个具体工厂只需要生产一种具体产品,但是在抽象工厂模式中一个具体工厂可以生产一组相关的具体产品,这样一组产品被称为产品族。产品族中的每一个产品都分属于某一个产品继承等级结构。
3.2、特点
抽象工厂模式中一个具体工厂能生产一组相关的具体产品(抽象工厂中的具体工厂负责生产一个产品族)
-
产品族:一组相关的具体产品(如海尔旗下的众多调味品)。
-
产品等级结构:每一个产品族都分属于某一个产品继承等级结构(如耗油下的多种品牌)。
3.3、主要角色
-
抽象工厂:提供创建产品的接口,调用者通过访问接口访问具体工厂的工厂方法来创建产品 。
-
具体工厂:实现抽象工厂的抽象方法,完成具体产品的创建。
-
抽象产品:定义了产品的规范,描述了产品的主要特性和功能。
-
具体产品 :实现了抽象产品所定义的接口,由具体工厂创建,它与具体工厂之间是一对多关系 (一个具体工厂生产多件产品)。
3.4、UML 图
3.5、实现
Soys(抽象产品):酱油工厂
public interface Soys {
void displaySoys(); // 显示酱油信息
}
OysterSauces(抽象产品):蚝油工厂
public interface OysterSauces {
void displayOysterSauces(); // 展示耗油信息
}
HaDaySoys(具体产品):海天酱油
public class HaDaySoys implements Soys{
@Override
public void displaySoys() {
System.out.println("海天酱油!!!");
}
}
HaDayOysterSauces(具体产品):海天耗油
public class HaDayOysterSauces implements OysterSauces{
@Override
public void displayOysterSauces() {
System.out.println("海天耗油!!!");
}
}
QianHeSoys():千禾酱油
public class QianHeSoys implements Soys{
@Override
public void displaySoys() {
System.out.println("千禾酱油!!!");
}
}
QianHeOysterSauces():千禾耗油
public class QianHeOysterSauces implements OysterSauces {
@Override
public void displayOysterSauces() {
System.out.println("千禾耗油!!!");
}
}
SeasoningFactory(抽象工厂):调味品工厂
public interface SeasoningFactory {
Soys createSoys();
OysterSauces createOysterSauces();
}
HaDayFactory(具体工厂):海天工厂
public class HaDayFactory implements SeasoningFactory {
@Override
public Soys createSoys() {
return new HaDaySoys();
}
@Override
public OysterSauces createOysterSauces() {
return new HaDayOysterSauces();
}
}
QianHeFactory(具体工厂):千禾工厂
public class QianHeFactory implements SeasoningFactory {
@Override
public Soys createSoys() {
return new QianHeSoys();
}
@Override
public OysterSauces createOysterSauces() {
return new QianHeOysterSauces();
}
}
3.6、测试代码
public class TestFactory {
@Test
public void testExample02(){
ClientSeasoning client = new ClientSeasoning(new HaDayFactory());
Soys soys = client.getSoys();
OysterSauces oysterSauces = client.getOysterSauces();
soys.displaySoys();
oysterSauces.displayOysterSauces();
}
}
3.7、测试效果
3.8、总结
抽象工厂模式优缺点
优点:
- 对于不同产品系列有比较多共性特征时,可以使用抽象工厂模式,有助于提升组件的复用性。
- 当需要提升代码的扩展性并降低维护成本时,把对象的创建和使用过程分开,能有效地将代码统一到一个级别上。
- 解决跨平台带来的兼容性问题。
缺点:
- 增加新的产品等级结构麻烦,需要对原有结构进行较大的修改,甚至需要修改抽象层代码,这显然会带来较大不变,违背了开闭原则。
应用场景
抽象工厂模式向使用(客户)方隐藏了下列变化:
- 程序所支持的实例集合(具体工厂)的数目;
- 当前是使用的实例集合中的哪一个实例;
- 在任意给定时刻被实例化的具体类型;
所以说,在理解抽象工厂模式原理时,要牢记“如何找到某一个类产品的正确共性功能”这个重点。
4、建造者模式
4.1、定义
将一个复杂对象的构建与表示分离,使得同样的构建过程可以创建不同的表示。
比如:一辆汽车是由多个部件组成的,包括了车轮、方向盘、发动机等等。对于大多数用户而言,并不需要知道这些部件的装配细节,并且几乎不会使用单独某个部件,而是使用一辆完整的汽车。而建造者模式就是负责将这些部件进行组装让后将完整的汽车返回给用户。
4.2、特点
创建一种类型的复杂对象,通过设置不同参数,定制化地创建不同对象。
4.3、实现
- 实现代码(以静态内部类的方式保证对象的密闭性,将set方法的返回设置为this,在build()方法中判断前置条件,最后返回RabbitMQClient对象,并将Builder对象传入)
public class RabbitMQClient {
// 私有构造,目标类的构造方法要传入一个Builder对象
private RabbitMQClient(Builder builder){
}
// builder类位于目标类的内部,并且使用static修饰
public static class Builder{
// 保证不可变对象的密闭性
private String host = "127.0.0.1";
private int port = 5672;
private int mode;
private String exchange;
private String queue;
private boolean isDurable = true;
int connectTime = 1000;
public String getHost() {
return host;
}
public Builder setHost(String host) {
this.host = host;
return this;
}
public int getPort() {
return port;
}
public Builder setPort(int port) {
this.port = port;
return this;
}
public int getMode() {
return mode;
}
public Builder setMode(int mode) {
this.mode = mode;
return this;
}
public String getExchange() {
return exchange;
}
public Builder setExchange(String exchange) {
this.exchange = exchange;
return this;
}
public String getQueue() {
return queue;
}
public Builder setQueue(String queue) {
this.queue = queue;
return this;
}
public boolean isDurable() {
return isDurable;
}
public Builder setDurable(boolean durable) {
isDurable = durable;
return this;
}
public int getConnectTime() {
return connectTime;
}
public Builder setConnectTime(int connectTime) {
this.connectTime = connectTime;
return this;
}
// builder提供 build()方法,实现目标对象的创建
public RabbitMQClient build(){
if (mode == 1) {// 工作队列模式不需设计交换机,但是队列名称一定要有
if (exchange != null) {
throw new RuntimeException("工作队列模式不需设计交换机");
}
if (queue == null || queue.trim().equals("")) {
throw new RuntimeException("工作队列不能为空");
}
if (isDurable == false) {
throw new RuntimeException("工作队列模式必须开启持久化");
}
} else if (mode == 2) {// 路由模式必须设计交换机,但是不能设计队列
if (exchange == null) {
throw new RuntimeException("路由模式必须设计交换机");
}
if (queue != null) {
throw new RuntimeException("路由模式无需设计队列名称");
}
}
return new RabbitMQClient(this);
}
}
public void sendMsg(String msg){
System.out.println("发送消息:" + msg);
}
}
4.3、测试代码
public class Test01 {
public static void main(String[] args) {
// 获取连接对象
RabbitMQClient client = new RabbitMQClient.Builder()
.setHost("192.168.52.123")
.setMode(1)
.setPort(5672)
.setQueue("test")
.build();
client.sendMsg("qqqq6666");
}
}
4.4、总结
建造者模式与工厂模式区别
-
工厂模式是用来创建不同但是相关类型的对象(继承同一父类或者接口的一组子类),由给定的参数来决定创建哪种类型的对象。
-
建造者模式是用来创建一种类型的复杂对象,通过设置不同的可选参数,“定制化”地创建不同的对象。
举例: 顾客走进一家餐馆点餐,我们利用工厂模式,根据用户不同的选择,来制作不同的食物,比如披萨、汉堡、沙拉。对于披萨来说,用户又有各种配料可以定制,比如奶酪、西红柿、起司,我们通过建造者模式根据用户选择的不同配料来制作披萨。
建造者模式的优缺点
优点:
- 建造者模式的封装性很好。使用建造者模式可以有效的封装变化,在使用建造者模式的场景中,一般产品类和建造者类是比较稳定的,因此,将主要的业务逻辑封装在指挥者类中对整体而言可以取得比较好的稳定性。
- 在建造者模式中,客户端不必知道产品内部组成的细节,将产品本身与产品的创建过程解耦,使得相同的创建过程可以创建不同的产品对象。
- 可以更加精细地控制产品的创建过程 。将复杂产品的创建步骤分解在不同的方法中,使得创建过程更加清晰,也更方便使用程序来控制创建过程。
- 建造者模式很容易进行扩展。如果有新的需求,通过实现一个新的建造者类就可以完成,基本上不用修改之前已经测试通过的代码,因此也就不会对原有功能引入风险。符合开闭原则。
缺点:
- 建造者模式所创建的产品一般具有较多的共同点,其组成部分相似,如果产品之间的差异性很大,则不适合使用建造者模式,因此其使用范围受到一定的限制。
应用场景
- 建造者(Builder)模式创建的是复杂对象,其产品的各个部分经常面临着剧烈的变化,但将它们组合在一起的算法却相对稳定,所以它通常在以下场合使用。
- 创建的对象较复杂,由多个部件构成,各部件面临着复杂的变化,但构件间的建造顺序是稳定的。
- 创建复杂对象的算法独立于该对象的组成部分以及它们的装配方式,即产品的构建过程和最终的表示是独立的。
5、原型模式
5.1、定义
通过一个已经创建的实例作为原型,通过复制该原型对象创建一个和原型对象相同的新对象(类似孙悟空的毫毛变猴子)。
5.2、应用场景
创建对象的成本比较大的时候(如从耗时较长的计算或者从查询耗时长的RPC
接口获取数据),直接拷贝已有对象,而不是每次耗时创建对象。
5.3、浅拷贝
5.3.1、定义
克隆对象所有变量的值与原型对象完全一致(相应的引用数据类型的地址也是一致的,没有深拷贝,如果是深拷贝那么引用数据类型的地址应该是不同的)
5.3.2、实现
实现Cloneable
表示该类可以拷贝,利用父类Object
的clone
方法实现浅拷贝。
ConcretePrototype(具体原型对象):
public class ConcretePrototype implements Cloneable {
private Person person;
public void show(){
System.out.println("名称:" + person.getName());
}
public ConcretePrototype() {
System.out.println("具体原型对象创建成功!");
}
@Override
protected ConcretePrototype clone() throws CloneNotSupportedException {
System.out.println("克隆对象复制成功!");
return (ConcretePrototype) super.clone();
}
public Person getPerson() {
return person;
}
public void setPerson(Person person) {
this.person = person;
}
}
Person(实体类):
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Person implements Serializable {
private String name;
}
5.3.3、测试代码
public class TestPrototype {
@Test
public void test01() throws CloneNotSupportedException {
ConcretePrototype c1 = new ConcretePrototype();
ConcretePrototype c2 = c1.clone();
System.out.println("c1和c2是同一个对象吗?---->" + (c1 == c2));
}
}
5.3.4、测试效果
5.4、深拷贝
5.4.1、定义
克隆对象所有变量的值与原型对象完全一致(不包含引用数据类型)
5.4.2、实现
通过序列化的输入输出实现深拷贝。
@Test
public void test04() throws Exception {
ConcretePrototype c1 = new ConcretePrototype();
Person p1 = new Person("张三");
c1.setPerson(p1);
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("obj.txt"));
oos.writeObject(c1);
oos.flush();
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("obj.txt"));
ConcretePrototype c2 = (ConcretePrototype) ois.readObject();
Person p2 = c2.getPerson();
p2.setName("李四");
c1.show();
c2.show();
System.out.println("c1和c2是同一个对象吗?---->" + (p1 == p2));
oos.close();
ois.close();
}
5.4.3、测试效果
5.5、总结
原型模式优缺点
优点:
1、 当创建新的对象实例较为复杂时,使用原型模式可以简化对象的创建过程, 通过复制一个已有实例可以提高新实例的创建效率.
比如,在 AI 系统中,我们经常需要频繁使用大量不同分类的数据模型文件,在对这一类文件建立对象模型时,不仅会长时间占用 IO 读写资源,还会消耗大量 CPU 运算资源,如果频繁创建模型对象,就会很容易造成服务器 CPU 被打满而导致系统宕机。通过原型模式我们可以很容易地解决这个问题,当我们完成对象的第一次初始化后,新创建的对象便使用对象拷贝(在内存中进行二进制流的拷贝),虽然拷贝也会消耗一定资源,但是相比初始化的外部读写和运算来说,内存拷贝消耗会小很多,而且速度快很多
2、原型模式提供了简化的创建结构,工厂方法模式常常需要有一个与产品类等级结构相同的工厂等级结构(具体工厂对应具体产品),而原型模式就不需要这样,原型模式的产品复制是通过封装在原型类中的克隆方法实现的,无须专门的工厂类来创建产品.
3、可以使用深克隆的方式保存对象状态,使用原型模式将对象复制一份并将其状态保存起来,以便在需要的时候使用,比如恢复到某一历史状态,可以辅助实现撤销操作.
在某些需要保存历史状态的场景中,比如,聊天消息、上线发布流程、需要撤销操作的程序等,原型模式能快速地复制现有对象的状态并留存副本,方便快速地回滚到上一次保存或最初的状态,避免因网络延迟、误操作等原因而造成数据的不可恢复。
缺点:
- 需要为每一个类配备一个克隆方法,而且该克隆方法位于一个类的内部,当对已有的类进行改造时需要修改源代码,违背了开闭原则.
应用场景
-
资源优化场景。也就是当进行对象初始化需要使用很多外部资源时,比如,IO 资源、数据文件、CPU、网络和内存等。
-
复杂的依赖场景。 比如,F 对象的创建依赖 A,A 又依赖 B,B 又依赖 C……于是创建过程是一连串对象的 get 和 set。
-
性能和安全要求的场景。 比如,同一个用户在一个会话周期里,可能会反复登录平台或使用某些受限的功能,每一次访问请求都会访问授权服务器进行授权,但如果每次都通过 new 产生一个对象会非常烦琐,这时则可以使用原型模式。
-
同一个对象可能被多个修改者使用的场景。 比如,一个商品对象需要提供给物流、会员、订单等多个服务访问,而且各个调用者可能都需要修改其值时,就可以考虑使用原型模式。
-
需要保存原始对象状态的场景。 比如,记录历史操作的场景中,就可以通过原型模式快速保存记录。
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)