百日筑基第二十三天-23种设计模式-创建型总汇

百日筑基第二十三天-23种设计模式-创建型总汇

前言

设计模式可以说是对于七大设计原则的实现。

总体来说设计模式分为三大类:

  • 创建型模式,共五种:单例模式、简单工厂模式、抽象工厂模式、建造者模式、原型模式。
  • 结构型模式,共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
  • 行为型模式,共十一种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。

23种设计模式的关系图:
在这里插入图片描述

单例模式

简介

单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建(私有的构造器),这个类对外提供了唯一一种访问其实例的方式,对外只能够直接访问,不能实例化此类对象。例如,一台计算机上可以连接多台打印机,但是这个计算机上的打印程序只能有一个,这里就可以通过单例模式来避免两个打印作业同时输出到打印机中,即在整个的打印过程中只有一个打印程序的实例。

简单点说,单例模式(也叫单件模式)的作用就是保证在整个应用程序的生命周期中,任何时刻,单例类的实例都最多只存在一个。单例模式确保某一个类只有一个实例,而且自行实例化,并向整个系统提供这个实例单例模式。单例模式只应在有真正的“单一实例”需求时才可以使用。

单例的类图如下:其中 uniqueInstance 持有唯一的单例实例,类方法 getInstance() 用来获取唯一的实例化对象。

 Singleton
——————————————————————
-static uniqueInstance
-other Attribute
——————————————————————
+static genInstance()()
+otherMethods()

8 种单例实现方式(有点多但有必要都看)

【1】饿汉式(静态常量)优缺点:
 ● 优点:这种写法比较简单,就是在类装载的时候就完成实例化。避免了线程同步问题。没有加锁,执行效率会提高。
 ● 缺点:在类装载的时候就完成实例化,没有达到 Lazy Loading 的效果。如果从始至终从未使用过这个实例,则会造成内存浪费,容易产生垃圾对象。
 ● 这种方式基于 classload 机制避免了多线程同步问题,instance 在类装载时就实例化,在单例模式中大多数都是调用 getInstance 方法,但是导致类装载的原因有很多种,因此通过其他方式(或者其他静态方法)导致类装载,此时初始化 instance就没有达到 Lazy Loading 的效果。

public class SingleTon {//将构造器私有化,防止直接 Newprivate SingleTon(){}//创建好一个私有的 SingleTon 实例private static SingleTon instance = new SingleTon();//提供一个 public 的静态方法, 可以返回 instancepublic static SingleTon getInstance() {return instance;}
}

【2】饿汉式(静态代码块)优缺点:
 ● 这种方式和上面的方式其实类似,只不过将类实例化的过程放在了静态代码块中,也是在类装载的时候,就执行静态代码块中的代码,初始化类的实例。优缺点和上面一样。
 ● 结论:这种单例模式可用,但可能造成内存浪费。

public class SingleTon {//将构造器私有化,防止直接 Newprivate SingleTon(){}//创建好一个私有的 SingleTon 实例private static SingleTon instance;//静态块static {instance = new SingleTon();}//提供一个 public 的静态方法, 可以返回 instancepublic static SingleTon getInstance() {return instance;}
}

【3】懒汉式(线程不安全)优缺点:

● 起到了 Lazy Loading 的效果,但是只能在单线程下使用。
 ● 如果在多线程下,一个线程进入if(singleton == null)判断语句块,还未来得及创建,另一个线程也通过了上述判断语句,这时便产生了多个实例。所以在多线程环境下不可使用这种方式。
 ● 结论:在实际开发中,不要使用这种方法。

public class SingleTon {//将构造器私有化,防止直接 Newprivate SingleTon(){}//创建好一个私有的 SingleTon 实例private static SingleTon instance;//提供一个 public 的静态方法, 可以返回 instancepublic static SingleTon getInstance() {if(instance == null) {instance = new SingleTon();}return instance;}
}

【4】懒汉式(线程安全,同步方法)优缺点:
 ● 解决了线程不安全问题。
 ● 效率太低了,每个线程在想获得类的实例的时候,执行 getInstance() 方法都要进行同步。而其实这个方法只执行一次实例化代码就够了,后面的想获得实例,直接 return 就够了。方法进行同步效率太低。
 ● 结论:在实际开发中不推荐使用。

public class SingleTon {//将构造器私有化,防止直接 Newprivate SingleTon(){}//创建好一个私有的 SingleTon 实例private static SingleTon instance;//提供一个 public 的静态方法, 可以返回 instancepublic static synchronized SingleTon getInstance() {if(instance == null) {instance = new SingleTon();}return instance;}
}

【5】懒汉式(线程安全,同步代码块)优缺点:
 ● 这种方式,本意是想对第四种实现方式的改进,因为前面同步方法效率太低, 改为同步产生实例化的的代码块。
 ● 但是这种同步并不能起到线程同步的作用。跟第3种实现方式遇到的情形一致,假如一个线程进入了 if (singleton == null) 判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例。
 ● 结论:在实际开发中,不能使用这种方式。

public class SingleTon {//将构造器私有化,防止直接 Newprivate SingleTon(){}//创建好一个私有的 SingleTon 实例private static SingleTon instance;//提供一个 public 的静态方法, 可以返回 instancepublic static SingleTon getInstance() {if(instance == null) {//添加同步代码块,提高了效率,多线程时存在创建的对象不一致风险synchronized(SingleTon.class) {instance = new SingleTon();}}return instance;}
}

【7】静态内部类优缺点:
 ● 这种方式采用了类加载器的机制来保证初始化实例时只有一个线程。
 ● 静态内部类方式在 SingleTon 类(父类)被装载时,不会导致内部类被装载,也就不会立即实例化,属于懒加载类型。当调用 getInstance() 方法时,才会装载 SingleTonInstance 类,从而完成 SingleTon 的实例化。
 ● 类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM 帮我们保证了线程的安全,在类初始化时,别的线程无法进入。
 ● 避免了线程不安全,利用静态内部类特点实现延迟加载,效率高。
 ● 结论:推荐使用。

public class SingleTon {//将构造器私有化,防止直接 Newprivate SingleTon(){}//在内部内中创建一个对象的实例,当父类 SingleTon 加载时,内部类 SingleTonInstance 无需加载private static class SingleTonInstance{private static final SingleTon INSTANCE = new SingleTon();}//提供一个 public 的静态方法, 可以返回 SingleTon实例public static SingleTon getInstance() {return SingleTonInstance.INSTANCE;}
}

【8】枚举优缺点:
 ● 这借助JDK1.5中添加的枚举来实现单例模式。不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象。
 ● 这种方式是Effective Java作者Josh Bloch 提倡的方式
 ● 结论:推荐使用

enum SingleTon {//当只有一个对象时,就是单例INSTANCE;
}

单例模式注意事项和使用场景

【1】单例模式保证了系统内存中该内只存在一个对象,节省了系统资源,对于一些需要频繁创建和销毁的对象,使用单例模式可以提高系统性能。
【2】当想实例化一个单例类的时候,必须要记住使用相应的获取对象的方法,而不是使用 new。
【3】单例模式使用场景:

  • 需要频繁的进行创建和销毁对象
  • 创建对象时耗时过多或消耗过多资源(既重量级对象)但又常使用的对象
  • 工具类对象
  • 频繁访问数据库或文件的对象(比如数据源、session工厂等)

工厂模式

简介

工厂模式(Factory Pattern)是 Java 中最常用的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。在工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。简单工厂模式是由一个工厂对象决定创建出哪一种产品类的实例。简单工厂模式是工厂模式家族中最简单使用的模式。简单工厂模式定义了一个创建对象的类,由这个类来封装实例化对象的行为。在软件开发中,当我们会用到大量的创建某种、某类或者某批量对象时,就会使用到工厂模式。

简单工厂模式案例

在这里插入图片描述

【1】定义一个接口 Pizza

package com.yintong.principle.singleresponsibility;
//将Pizza 做成一个接口
public interface Pizza {//准备原材料, 不同的披萨不一样,因此,我们做成抽象方法public void prepare();
}

【2】定义披萨的种类,实现 Pizza 接口,如下定义了 GREEK、CHEESE 两种披萨。如果后续有新的种类可以实现 Pizza 接口,继续添加新的品种。

//希腊披萨
public class GreekPizza implements Pizza{@Overridepublic void prepare() {System.out.println(" 给希腊披萨 准备原材料 ");}
}
//奶酪披萨
public class CheesePizza implements Pizza{@Overridepublic void prepare() {System.out.println(" 给制作奶酪披萨 准备原材料 ");}
}

【3】 创建一个简单的工厂类对象 SimpleFactory

public class SimpleFactory {//传入 orderType(种类) 返回对应的 Pizza 对象public Pizza createPizza(String orderType) {Pizza pizza = null;//使用简单工厂模式if (orderType.equals("greek")) {//希腊披萨pizza = new GreekPizza();} else if (orderType.equals("cheese")) {//奶酪披萨pizza = new CheesePizza();}return pizza;}
}

【4】在应用代码 OrderPizza 中调用工厂类,获取用户需要的披萨对象。

public class OrderPizza {//定义用户要获取的披萨类型private static final String pizza = "greek";public static void main(String[] args) {//通过工厂获取希腊披萨SimpleFactory simpleFactory = new SimpleFactory();//greekPizza 就是我们需要获取的目标披萨Pizza greekPizza = simpleFactory.createPizza(pizza);//调用目标披萨的方法greekPizza.prepare();}
}

简单工厂模式的优缺点

优点:
1)一个调用者想创建一个对象,只要知道其名称就可以了。
2)扩展性高,如果想增加一个产品,只要扩展一个工厂类就可以。
3)屏蔽产品的具体实现,调用者只关心产品接口。

缺点: 每增加一个产品时,都需要增加一个具体实现类和对象实现工厂,使得系统中类的个数成倍增加,在一定程度上增加了系统的复杂度,同时也增加了系统具体类的依赖。这并不是什么好事。

注意事项: 作为一种创建类模式,在任何需要生成复杂对象的地方,都可以使用工厂方法模式。有一点需要注意的地方就是复杂对象适合使用工厂模式,而简单对象,特别只需要通过 new 就可以完成创建的对象,无需工厂模式。如果使用工厂模式就需要使用工厂类,会增加系统的复杂度。

抽象工厂方法模式

需求: 在上述的基础上,添加了地区。例如:用户可以点北京的奶酪披萨,北京的胡椒披萨,或者伦敦的奶酪披萨,伦敦的胡椒披萨。

工厂方法设计模式:
1)、定义一个 interface(工厂的接口) 用于创建相关或者依赖关系的对象簇,而无需指明具体对象类。
2)、从设计层面来看,抽象工厂模式就是简单工厂模式的改进(或者称为进一步抽象)。
3)、将工厂抽象成两层,AbsFactory(抽象工厂)和具体实现的工厂子类。程序员可以根据创建对象类型使用对应的工厂子类。这样将单个的简单工厂类变成了工厂簇,更有利于代码的维护和扩展。

类图: 将简单工厂模式中的工厂类抽象成了一个接口,易于后期扩展。

在这里插入图片描述

【1】定义工厂类的接口(AbsFactory)

//一个抽象工厂模式的抽象层(接口)
public interface AbsFactory {//让下面的工厂子类来 具体实现public Pizza createPizza(String orderType);
}

【2】让北京和伦敦的工厂类实现此接口(与简单工厂类的区别就是实现了 AbsFactory 接口,伦敦略)

public class BJFactory implements AbsFactory{//更加orderType 返回对应的Pizza 对象public Pizza createPizza(String orderType) {Pizza pizza = null;//使用简单工厂模式if (orderType.equals("greek")) {//希腊披萨pizza = new GreekPizza();} else if (orderType.equals("cheese")) {//奶酪披萨pizza = new CheesePizza();}return pizza;}
}

【3】在 Order 订单应用中,与接口 AbsFactory 进行组合。

public class OrderPizza {AbsFactory factory;//定义构造器public OrderPizza(AbsFactory factory){this.factory = factory;}//定义用户要获取的披萨类型,可以定义一个变量从客户端传入private static final String pizza = "greek";public void main(String[] args) {//greekPizza 就是我们需要获取的目标披萨Pizza greekPizza = factory.createPizza(pizza);//调用目标披萨的方法greekPizza.prepare();}
}

工厂模式在 JDK-Calendar 类中的应用

//1、调用 Calendar 对象
Calendar instance = Calendar.getInstance();
//2、进入 Calendar.getInstance() 方法
public static Calendar getInstance()
{return createCalendar(TimeZone.getDefault(),Locale.getDefault(Locale.Category.FORMAT));
}
//3、进入 createCalendar 方法
private static Calendar createCalendar(TimeZone zone,Locale aLocale){CalendarProvider provider =LocaleProviderAdapter.getAdapter(CalendarProvider.class, aLocale).getCalendarProvider();if (provider != null) {try {return provider.getInstance(zone, aLocale);} catch (IllegalArgumentException iae) {// fall back to the default instantiation}}Calendar cal = null;if (aLocale.hasExtensions()) {String caltype = aLocale.getUnicodeLocaleType("ca");if (caltype != null) {//这里就是我们要重点查看的部分,根据用户需求,创建Calendar 对象switch (caltype) {case "buddhist":cal = new BuddhistCalendar(zone, aLocale);break;case "japanese":cal = new JapaneseImperialCalendar(zone, aLocale);break;case "gregory":cal = new GregorianCalendar(zone, aLocale);break;}}}return cal;
}

工厂模式小结

1)工厂模式的意义: 将实例化对象的代码提取出来,放到一个类中统一管理和维护,达到和主项目的依赖关系的解耦。从而提高项目的扩展和维护性。
2)设计模式依赖抽象原则: 创建对象实例时,不要直接 new 类,而是把这个 new 类的动作放在一个工厂的方法中,并返回。不要让类继承具体类,而是继承抽象类或者实现 interface(接口)。不要覆盖基类中已经实现的方法。

建造者模式

简介

建造者模式(Builder Pattern): 使用简单的对象一步一步构建成一个复杂的对象。这种设计模式属于创建者模式,它提供了一种创建对象的最佳方式。一个 Builder 类会一步一步构造最终的对象。该 Builder 类是独立于其他对象的。例如,计算机是由 CPU、主板、内存、硬盘、显卡、机箱、显示器、键盘、鼠标等部件组装而成的,采购员不可能自己去组装计算机,而是将计算机的配置要求告诉计算机销售公司,计算机销售公司安排技术人员去组装计算机,然后再交给要买计算机的采购员。

以上所有这些产品都是由多个部件构成的,各个部件可以灵活选择,但其创建步骤都大同小异。这类产品的创建无法用前面介绍的工厂模式描述,只有建造者模式可以很好地描述该类产品的创建。

  • 建造者模式: 又叫生成器模式,是一种对象构建模式。它可以将复杂对象的建造过程抽象出来(抽象类别),使这个抽象过程的不同实现方法可以构造出不同表现(属性)的对象。
  • 建造者模式,它允许用户只通过指定复杂对象的类型和内容就可以构建它们,用户不需要知道内部的具体构建细节。
  • 建造者模式主要解决在软件系统中,当面临一个复杂对象的创建工作时,其通常由各个部分的子对象用一定的算法构成;由于需求的变化,这个复杂对象的各个部分经常面临剧烈的变化,但是将它们组合在一起的算法却相对稳定。
  • 主要适用于:一些基本部件不会变,而其组合经常变化的情况。主要是将变与不变进行分离(去肯德基,汉堡、可乐、薯条、炸鸡翅等是不变的,而其组合是经常变化的,生成出所谓的"套餐")。

建造者模式的四个角色

1)Product(产品角色): 包含多个组成部件的复杂对象(产品对象)。
2)Builder(抽象建造者): 创建一个包含 Product 各个子部件的抽象方法或接口,通常还包含了一个返回复杂产品的方法。
3)ConcreteBuilder(具体建造者): 实现 Builder 接口,构建和装备各个子部件的具体实现。
4)Director(指挥者): 构建一个使用 Builder 接口的对象。它主要作用是用于创建一个复杂的对象。在指挥者中不涉及具体产品的信息主要分两个作用,一是隔离客户与对象的生产过程,二是负责控制对象的生产过程。

建造者模式的注意事项和细节

1)客户端不必知道产品内部组成的细节,将产品本身与产品的创建过程解耦,使得相同的创建过程可以创建不同的产品对象
2)每一个具体建造者都相对独立,因此可以方便的替换或者新增具体建造者,用户使用不同的具体建造者即可得到不同的产品对象。
3)可以更加精准的控制产品的创建过程。将复杂产品的创建步骤分解在不同的方法中,使得创建过程更加清晰,也更方便使用程序来控制创建过程。
4)增加新的具体建筑者无需修改原有类库代码,指挥者类针对抽象建造者类编程,系统扩展方便。符合 OCP 原则
5)建造者模式创建的产品具有较多的共同点,其组成部分相似,如果产品之间的差异性很大,则不适合建造者模式,因此其使用范围受到一定的限制。
6)如果产品的内部变化复杂,可能会导致需要定义很多具体建造者内来实现这种变化,导致系统变得庞大,因此这种情况下,考虑是否选择建造者模式。
7)抽象工厂模式 VS 建造者模式:抽象工厂模式实现对产品家族的创建,一个产品家族就是一系列产品。具有不同分类维度的产品组合,采用抽象工厂模式不需要关系构建过程,只关系产品由什么工厂生成即可。而建造者模式则是要求按照指定的蓝图建造产品,它主要目的是通过组装零配件而产生一个新产品。

建造者模式原理类图

在这里插入图片描述

建造者模式案例

【1】创建 Product 产品角色:

public class Product {private String CPU;//主板 mainboardprivate String mainboard;//内存 memoryprivate String memory;//.....略//生成get set 方法 略
}

【2】抽象建造者对象:

public abstract class Builder {//组合 产品对象protected Product product = new Product();//抽象方法public abstract void buildCpu();public abstract void buildMainboard();public abstract void buildMemory();//返回一个产品对象public Product getResult() {return product;}
}

【3】具体建造者对象:一般会有多个这种对象,都实现 Builder 抽象类。

public class ConcreteBuilder_huawei extends Builder{@Overridepublic void buildCpu() {product.setCPU("华为CPU");}@Overridepublic void buildMainboard() {product.setMainboard("华为主板");}@Overridepublic void buildMemory() {product.setMemory("华为内存");}}

【4】指挥者类,主要构建组装的流程:返回的是产品对象

public class Director {//需要将抽象类聚合进来Builder builder = null;//创建一个构造器 调用时传入具体的实现类public Director(Builder builder) {this.builder = builder;}//添加set 方法 用户可以通过此方法修改已有的 建造者对象public void setBuilder(Builder builder) {this.builder = builder;}//笔记本的组成流程  返回的是产品类public Product pack_pc() {builder.buildMainboard();builder.buildCpu();builder.buildMemory();return builder.getResult();}
}

【5】客户端通过指挥者类调用需要的品牌电脑:

public class Client {public static void main(String[] args) {//创建需要的电脑子部件——例如我们要组装的是 华为ConcreteBuilder_huawei huawei = new ConcreteBuilder_huawei();//调用指挥者类,将需要的品牌传入,根据控制着中的流程进行组装Director director = new Director(huawei);//调用组装方法,返回产品Product pack_pc = director.pack_pc();//查看输入的结果System.out.println(pack_pc.toString());//结果为:Product [CPU=华为CPU, mainboard=华为主板, memory=华为内存]}
}

源码分析(StringBuilder)

【1】java.lang.StringBuilder中的建造者模式:

public class OrginBuilder {public static void main(String[] args) {StringBuilder builder = new StringBuilder("hello");builder.append("world");}
}

【2】进入 append 方法,会发现 StringBuilder 类即就是我们所说的指挥者。建造方法的具体实现是由 AbstractStringBuilder 实现。

@Override
public StringBuilder append(String str) {super.append(str);return this;
}

【3】进入 AbstractStringBuilder 类,实现了 Appendable 接口方法,此类已是建造者,只是不能实例化。

public AbstractStringBuilder append(String str) {if (str == null)return appendNull();int len = str.length();ensureCapacityInternal(count + len);str.getChars(0, len, value, count);count += len;return this;
}

【4】Appendable 接口定义了多个 append 方法(抽象方法),既 Appendle 为抽象建造者,定义了抽象方法。

public interface Appendable {Appendable append(CharSequence csq) throws IOException;  ......
}

原型模式

简介

原型模式(Prototype Pattern): 是用于创建重复对象,同时又能保证性能。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。这种模式是实现了一个原型接口,该接口用于创建当前对象的克隆。当直接创建对象的代价比较大时,则采用这种模式。

思想: Java 中 Object 类是所有类的根类,Object 类提供了一个 clone() 方法,该方法可以将一个 Java 对象复制一份,但是需要实现 clone 的 Java 类必须要实现一个接口 Cloneable,该接口表示该类能够复制且具有复制的能力(原型模式)。

  • 原型模式:用原型实例指定创建对象的种类,并且通过拷贝这些原型,创建新的对象。
  • 原型模式是一种创建型设计模式,允许一个对象再创建另外一个可定制的对象,无需知道如何创建的细节。
  • 工作原理是:通过将一个原型对象传给那个要发动创建的对象,这个要发动创建的对象通过请求原型对像拷贝它们自己来实现创建,及对象的clone()。

原理结构图说明:
1)Prototype:原型类,声明一个克隆自己的接口 clone。
2)ConcretePrototype:具体的原型类,实现一个克隆自己的操作。
3)Client 让一个原型对象克隆自己,从而创建一个新的对象(相当于属性)。

在这里插入图片描述

原型模式案例分析

【1】克隆类需要实现 Cloneable 重写 clone 方法。

package com.yintong.principle.singleresponsibility;
//写一个手机的克隆类
public class ConcretePrototype implements Cloneable{//名称private String name;//号码private Long number;//构造器public ConcretePrototype(String name, Long number) {super();this.name = name;this.number = number;}@Overridepublic String toString() {return "ConcretePrototype [name=" + name + ", number=" + number + "]";}// 克隆用到的主要部分@Overrideprotected Object clone() throws CloneNotSupportedException {ConcretePrototype ConcretePrototype = null;try {ConcretePrototype = (ConcretePrototype) super.clone();}catch (Exception e) {System.out.println(e.getMessage());}return ConcretePrototype;}
}

【2】客户端调用 clone 方法,实现原型模式。

public class Client {public static void main(String[] args) throws CloneNotSupportedException {//创建一个对象ConcretePrototype prototype = new ConcretePrototype("华为", new Long(1568889932));//通过原型模式完成对象的创建  克隆ConcretePrototype p2 = (ConcretePrototype)prototype.clone();}
}

Spring 中的应用

【1】当 scope 配置为 prototype 时,表示原型模式。

 <!-- 这里我们的 scope="prototype" 即 原型模式来创建 --><bean id="id01" class="com.atguigu.spring.bean.Monster" scope="prototype"/>

【2】查看底层调用:

else if (mbd.isPrototype()) {// It's a prototype -> create a new instance.Object prototypeInstance = null;try {beforePrototypeCreation(beanName);prototypeInstance = createBean(beanName, mbd, args);}finally {afterPrototypeCreation(beanName);}// ***  创建一个代理类  ***bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
}

浅拷贝

● 对于数据类型是基本数据类型的成员变量,浅拷贝会直接进行值传递,也就是将该属性值复制一份给新对象。
● 对于数据类型是引用类型的成员变量,比如说成员变量是某个数组、某个类的对象等,那么浅拷贝会进行引用传值,也就是只是将成员变量的引用值(内存地址)复制一份给新对象。因为实际上两个对象的该成员变量都指向同一个实例。在这种情况下,在一个对象中修改该成员变量会影响到另一个对象的该成员变量值
● 浅拷贝是使用默认的 clone() 方法来实现。

深拷贝

● 复制对象的所有基本数据类型的成员变量值。
为所有引用数据类型的成员变量申请存储空间并复制每个引用数据类型成员变量所引用的对象,直到该对象可达的所有对象。也就是说,对象进行深拷贝要对整个对象进行拷贝。
● 深拷贝的实现方式有两种,第一种是重写 clone 方法来实现深拷贝,第二种是通过序列化实现深拷贝,也是推荐的一种。

深拷贝的两种实现方法

【1】通过调用引用类型的克隆方法,实现深拷贝。缺点就是当引用类型多时,不建议采用。

public class DeepClone implements Cloneable{//基本数据类型private String name;//引用数据类型private Spare spare;//重写clone方法,调用引用类型的克隆方法@Overrideprotected Object clone() throws CloneNotSupportedException {DeepClone deepClone = null;deepClone = (DeepClone)super.clone();//克隆引用数据类型deepClone.spare = (Spare) spare.clone();return deepClone;}
}

【2】通过序列化的方式,实现深拷贝:也是建议使用的方法。

public class DeepClone implements Serializable{/*** 序列化 ID*/private static final long serialVersionUID = 1L;//数据类型  略。。。。//重写clone方法,调用引用类型的克隆方法protected Object deepClone(){ByteArrayOutputStream BOStream = null;ObjectOutputStream OOSream = null;ByteArrayInputStream BIStream = null;ObjectInputStream OIStream =null;try {//序列化BOStream = new ByteArrayOutputStream();OOSream = new ObjectOutputStream(BOStream);//将当前对象写入流中OOSream.writeObject(this);//反序列化BIStream = new ByteArrayInputStream(BOStream.toByteArray());OIStream = new ObjectInputStream(BIStream);DeepClone deepClone = (DeepClone) OIStream.readObject();return deepClone;} catch (Exception e) {e.printStackTrace();return null;}finally {try {BOStream.close();OOSream.close();BIStream.close();OIStream.close();} catch (Exception e2) {e2.printStackTrace();}}}
}

原型模式的注意事项和细节

1)创建新的对象比较复杂时,可以利用原型模式简化对象的创建过程,同时也能够提高效率。
2)不用重新初始化对象,而是动态地获得对象运行时的状态。
3)如果原始对象发生变化(增加或者减少属性),其他克隆对象也会发生变化,无需修改代码。
4)在实现深克隆的时候可能需要比较复杂的代码。
5)缺点:需要为每一个配置类配置一个克隆方法,这对全新的类来说不是很难,但对已有的类进行改造时,需要修改其源代码,违背了 ocp 原则

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/378953.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Hadoop3:HDFS存储优化之小文件归档

一、情景说明 我们知道&#xff0c;NameNode存储一个文件元数据&#xff0c;默认是150byte大小的内存空间。 那么&#xff0c;如果出现很多的小文件&#xff0c;就会导致NameNode的内存占用。 但注意&#xff0c;存储小文件所需要的磁盘容量和数据块的大小无关。 例如&#x…

SpringBoot整合阿里云RocketMQ对接,商业版

1.需要阿里云开通商业版RocketMQ 普通消息新建普通主题,普通组,延迟消息新建延迟消息主题,延迟消息组 2.结构目录 3.引入依赖 <!--阿里云RocketMq整合--><dependency><groupId>com.aliyun.openservices</groupId><artifactId>ons-client</…

Unity3d开发google chrome的dinosaur游戏

游戏效果 游戏中&#xff1a; 游戏中止&#xff1a; 一、制作参考 如何制作游戏&#xff1f;【15分钟】教会你制作Unity小恐龙游戏&#xff01;新手15分钟内马上学会&#xff01;_ unity教学 _ 制作游戏 _ 游戏开发_哔哩哔哩_bilibili 二、图片资源 https://download.csdn.…

MySQL 事务与锁

事务ACID特性 原子性&#xff1a;事务要么同时成功&#xff0c;要么同时失败&#xff0c;事务的原子性通过undo log日志保证 一致性&#xff1a;业务代码要抛出报错&#xff0c;让数据库回滚 隔离性&#xff1a;事务并发执行时&#xff0c;他们内部操作不能互相干扰 持久性&…

数据分析01——系统认识数据分析

1.数据分析的全貌 1.1观测 1.1.1 观察 &#xff08;1&#xff09;采集数据 a.采集数据&#xff1a;解析系统日志 当你在看视频的时候———就会产生日志———解析日志———得到数据 b.采集数据&#xff1a;埋点获取新数据&#xff08;自定义记录新的信息&#xff09; 日志…

JavaScript学习笔记(九)

56、JavaScript 类 56.1 JavaScript 类的语法 请使用关键字 class 创建一个类。 请始终添加一个名为 constructor() 的方法。 JavaScript 类不是对象。 它是 JavaScript 对象的模板。 语法&#xff1a; class ClassName {constructor() { ... } }示例&#xff1a;例子创…

【学习笔记】无人机(UAV)在3GPP系统中的增强支持(六)-人工智能控制的自主无人机用例

引言 本文是3GPP TR 22.829 V17.1.0技术报告&#xff0c;专注于无人机&#xff08;UAV&#xff09;在3GPP系统中的增强支持。文章提出了多个无人机应用场景&#xff0c;分析了相应的能力要求&#xff0c;并建议了新的服务级别要求和关键性能指标&#xff08;KPIs&#xff09;。…

MongoDB教程(六):mongoDB复制副本集

&#x1f49d;&#x1f49d;&#x1f49d;首先&#xff0c;欢迎各位来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里不仅可以有所收获&#xff0c;同时也能感受到一份轻松欢乐的氛围&#xff0c;祝你生活愉快&#xff01; 文章目录 引言一、MongoD…

在 vite+vue3+electron 中使用 express

文章目录 一、Vite Vue3 Electron 项目的搭建二、搭建 express 环境1、安装 express 框架所需依赖2、创建 express 项目3、配置路由4、启动 express 服务5、启动 electron 并获取数据 三、项目打包 一、Vite Vue3 Electron 项目的搭建 详细的项目构建和打包可参考另一篇文…

c语言 Program to print pyramid pattern (打印金字塔图案的程序)

编写程序打印由星星组成的金字塔图案 例子 &#xff1a; 输入&#xff1a;n 6输出&#xff1a; * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 我们强烈建…

MATLAB科研数据可视化教程

原文链接&#xff1a;MATLAB科研数据可视化https://mp.weixin.qq.com/s?__bizMzUzNTczMDMxMg&mid2247609462&idx3&snf7043936fc5ee42b833c7c9f3bcd24ba&chksmfa826d91cdf5e4872eb275e5319b66ba6927ea0074fb2293fe1ca47d6aedf38ab91050be484c&token1551213…

k8s集群 安装配置 Prometheus+grafana

k8s集群 安装配置 Prometheusgrafana k8s环境如下&#xff1a;机器规划&#xff1a; node-exporter组件安装和配置安装node-exporter通过node-exporter采集数据显示192.168.40.180主机cpu的使用情况显示192.168.40.180主机负载使用情况 Prometheus server安装和配置创建sa账号&…

Redis中数据分片与分片策略

概述 数据分片是一种将数据分割并存储在多个节点上的技术&#xff0c;可以有效提高系统的扩展性和性能。在Redis中&#xff0c;数据分片主要用于解决单个实例存储容量和性能瓶颈的问题。通过将数据分散存储到多个Redis节点中&#xff0c;可以将负载均衡到不同的服务器上&#…

ArcGIS Enterprise 命令行组件创建配置

1. 创建ArcGIS Server站点 使用 createsite工具 命令行直接执行 createsite.sh [-u <arg>] [-p <arg>] [-d <arg>] [-c <arg>]执行文件 createsite.sh [-f <FILE>]安装目录下会有类似的创建站点文件&#xff1a; 修改其中的内容&#xff0c;…

芯片基础 | Verilog结构级描述和操作符(上)

术语定义(Terms and Definitions) 结构描述(Structural Modeling) 用门及门的连接描述器件的功能基本单元(primitives原语) Verilog语言已定义的具有简单逻辑功能的功能模型(models)结构描述 结构描述等价于逻辑图,它们都是连接简单元件来构成更为复杂的元件;Verilog使用其连接…

PDF小工具poppler

1. 简介 介绍一下一个不错的PDF库poppler。poppler的官网地址在:https://poppler.freedesktop.org/ 它是一个PDF的渲染库,顾名思义,它的用途就是读取PDF文件,然后显示到屏幕(显示到屏幕上只是一种最狭义的应用,包括使用Windows上的GDI技术显示文件内容,当然可以渲染到…

智慧水利:迈向水资源管理的新时代,结合物联网、云计算等先进技术,阐述智慧水利解决方案在提升水灾害防控能力、优化水资源配置中的关键作用

本文关键词&#xff1a;智慧水利、智慧水利工程、智慧水利发展前景、智慧水利技术、智慧水利信息化系统、智慧水利解决方案、数字水利和智慧水利、数字水利工程、数字水利建设、数字水利概念、人水和协、智慧水库、智慧水库管理平台、智慧水库建设方案、智慧水库解决方案、智慧…

海外社媒矩阵为何会被关联?如何IP隔离?

在当今的数字时代&#xff0c;社交媒体已经成为人们日常生活中不可或缺的一部分。通过社交媒体&#xff0c;人们可以与朋友互动&#xff0c;分享生活&#xff0c;甚至进行业务推广和营销。然而&#xff0c;社交媒体账号关联问题逐渐受到广泛关注。社交媒体账号为何会关联&#…

C++ | Leetcode C++题解之第239题滑动窗口最大值

题目&#xff1a; 题解&#xff1a; class Solution { public:vector<int> maxSlidingWindow(vector<int>& nums, int k) {int n nums.size();vector<int> prefixMax(n), suffixMax(n);for (int i 0; i < n; i) {if (i % k 0) {prefixMax[i] num…

Oralce笔记-解决Oracle18c中ORA-28001: 口令已经失效

远程已经连不上了&#xff0c;需要登陆到安装Oracle的机器&#xff0c;使用sqlplus直接连。 sqlplus / as sysdba 登陆进去后修改期限为无限制&#xff1a; ALTER PROFILE DEFAULT LIMIT PASSWORD_LIFE_TIME UNLIMITED 对于已经告警提示密码已过期的数据库&#xff0c;需要…