一、创建型模式
- 创建型模式的作用就是创建对象
- 创建一个对象,最熟悉的就是 new 一个对象,然后 set 相关属性。但是,在很多场景下,需要给客户端提供更加友好的创建对象的方式,尤其是那种我们定义了类,但是需要提供给其他开发者用的时候
1.简单工厂模式
- 一个工厂类 XxxFactory,里面有一个静态方法,根据不同的参数,返回不同的派生自同一个父类(或实现同一接口)的实例对象
public class FoodFactory {
public static Food makeFood(String name) {
if (name.equals("noodle")) {
Food noodle = new LanZhouNoodle();
noodle.addSpicy("more");
return noodle;
} else if (name.equals("chicken")) {
Food chicken = new HuangMenChicken();
chicken.addCondiment("potato");
return chicken;
} else {
return null;
}
}
}
强调职责单一原则,一个类只提供一种功能,FoodFactory 的功能就是只要负责生产各种 Food
2、工厂模式
- 需要使用两个或两个以上的工厂
public interface FoodFactory {
Food makeFood(String name);
}
public class ChineseFoodFactory implements FoodFactory {
@Override
public Food makeFood(String name) {
if (name.equals("A")) {
return new ChineseFoodA();
} else if (name.equals("B")) {
return new ChineseFoodB();
} else {
return null;
}
}
}
public class AmericanFoodFactory implements FoodFactory {
@Override
public Food makeFood(String name) {
if (name.equals("A")) {
return new AmericanFoodA();
} else if (name.equals("B")) {
return new AmericanFoodB();
} else {
return null;
}
}
}
//ChineseFoodA、ChineseFoodB、AmericanFoodA、AmericanFoodB 都派生自 Food
客户端调用:
public class APP {
public static void main(String[] args) {
// 先选择一个具体的工厂
FoodFactory factory = new ChineseFoodFactory();
// 由第一步的工厂产生具体的对象,不同的工厂造出不一样的对象
Food food = factory.makeFood("A");
}
}
虽然都是调用 makeFood("A") 制作 A 类食物,但是,不同的工厂生产出来的完全不一样。
第一步,我们需要选取合适的工厂,然后第二步基本上和简单工厂一样。
核心在于,我们需要在第一步选好我们需要的工厂。比如,我们有 LogFactory 接口,实现类有 FileLogFactory 和 KafkaLogFactory,分别对应将日志写入文件和写入 Kafka 中,显然,我们客户端第一步就需要决定到底要实例化 FileLogFactory 还是 KafkaLogFactory,这将决定之后的所有的操作。
3、抽象工厂模式
- 涉及到产品族的时候,就需要引入抽象工厂模式
经典例子:造电脑
- 工厂模式:
因为电脑是由许多的构件组成的,我们将 CPU 和主板进行抽象,然后 CPU 由 CPUFactory 生产,主板由 MainBoardFactory 生产,然后,我们再将 CPU 和主板搭配起来组合在一起,如下图:
客户端调用:
// 得到 Intel 的 CPU
CPUFactory cpuFactory = new IntelCPUFactory();
CPU cpu = intelCPUFactory.makeCPU();
// 得到 AMD 的主板
MainBoardFactory mainBoardFactory = new AmdMainBoardFactory();
MainBoard mainBoard = mainBoardFactory.make();
// 组装 CPU 和主板
Computer computer = new Computer(cpu, mainBoard);
上述工厂模式优点:容易扩展,因为要给电脑加硬盘的话,只需要加一个 HardDiskFactory 和相应的实现即可,不需要修改现有的工厂
上述工厂模式存在问题:如果 Intel 家产的 CPU 和 AMD 产的主板不能兼容使用,那么这代码就容易出错,因为客户端并不知道它们不兼容,也就会错误地出现随意组合
为解决上述问题,引入产品族概念:
- 它代表了组成某个产品的一系列附件的集合
- 抽象工厂模式
涉及到这种产品族的问题的时候,就需要抽象工厂模式了。
在上述例子中,不再定义 CPU 工厂、主板工厂、硬盘工厂、显示屏工厂等等,直接定义电脑工厂,每个电脑工厂负责生产所有的设备,这样能保证肯定不存在兼容问题
这个时候,对于客户端来说,不再需要单独挑选 CPU厂商、主板厂商、硬盘厂商等,直接选择一家品牌工厂,品牌工厂会负责生产所有的东西,而且能保证肯定是兼容可用的:
public static void main(String[] args) {
// 第一步就要选定一个“大厂”
ComputerFactory cf = new AmdFactory();
// 从这个大厂造 CPU
CPU cpu = cf.makeCPU();
// 从这个大厂造主板
MainBoard board = cf.makeMainBoard();
// 从这个大厂造硬盘
HardDisk hardDisk = cf.makeHardDisk();
// 将同一个厂子出来的 CPU、主板、硬盘组装在一起
Computer result = new Computer(cpu, board, hardDisk);
}
抽象工厂的问题:
- 若要加个显示器,就需要修改所有的工厂,给所有的工厂都加上制造显示器的方法。这有点违反了对修改关闭,对扩展开放这个设计原则
4.单例模式
单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象
-
单例类只能有一个实例
-
单例类必须自己创建自己的唯一实例
-
单例类必须给所有其他对象提供这一实例
饿汉模式:
- 饿汉模式就是类一旦加载,就把单例初始化完成,保证getInstance的时候,单例是已经存在的了
public class SingletonEH {
/**
*是否 Lazy 初始化:否
*是否多线程安全:是
*实现难度:易
*描述:这种方式比较常用,但容易产生垃圾对象。
*优点:没有加锁,执行效率会提高。
*缺点:类加载时就初始化,浪费内存。
*它基于 classloder 机制避免了多线程的同步问题,
* 不过,instance 在类装载时就实例化,虽然导致类装载的原因有很多种,
* 在单例模式中大多数都是调用 getInstance 方法,
* 但是也不能确定有其他的方式(或者其他的静态方法)导致类装载,
* 这时候初始化 instance 显然没有达到 lazy loading 的效果。
*/
private static SingletonEH instance = new SingletonEH();
private SingletonEH (){}
public static SingletonEH getInstance() {
System.out.println("instance:"+instance);
System.out.println("加载饿汉式....");
return instance;
}
}
懒汉模式(饱汉模式):
- 懒汉模式比较懒,只有当调用getInstance的时候,才回去初始化这个单例
public class SingletonLHsyn {
/**
*是否 Lazy 初始化:是
*是否多线程安全:是
*实现难度:易
*描述:这种方式具备很好的 lazy loading,
*能够在多线程中很好的工作,但是,效率很低,99% 情况下不需要同步。
*优点:第一次调用才初始化,避免内存浪费。
*缺点:必须加锁 synchronized 才能保证单例,但加锁会影响效率。
*getInstance() 的性能对应用程序不是很关键(该方法使用不太频繁)。
*/
private static SingletonLHsyn instance;
private SingletonLHsyn (){}
public static synchronized SingletonLHsyn getInstance() {
if (instance == null) {
instance = new SingletonLHsyn();
}
return instance;
}
}
两者比较:
- 线程安全:
- 饿汉模式天生就是线程安全的,可以直接用于多线程而不会出现问题
- 懒汉模式本身是非线程安全的,为了实现线程安全要加锁synchronized或使用嵌套类
- 懒汉模式通过加锁synchronized实现线程安全后存在双重检查:
- 两次检查 instance 是否为 null
加锁synchronized:
public class Singleton {
/**
*是否 Lazy 初始化:是
*是否多线程安全:是
*实现难度:易
*描述:这种方式具备很好的 lazy loading,
*能够在多线程中很好的工作,但是,效率很低,99% 情况下不需要同步。
*优点:第一次调用才初始化,避免内存浪费。
*缺点:必须加锁 synchronized 才能保证单例,但加锁会影响效率。
*getInstance() 的性能对应用程序不是很关键(该方法使用不太频繁)。
*/
// 首先,也是先堵死 new Singleton() 这条路
private Singleton() {}
// 和饿汉模式相比,这边不需要先实例化出来,注意这里的 volatile,它是必须的
private static volatile Singleton instance = null;
public static Singleton getInstance() {
if (instance == null) {
// 加锁
synchronized (Singleton.class) {
// 这一次判断也是必须的,不然会有并发问题
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
嵌套类最经典:
public class Singleton3 {
private Singleton3() {}
// 主要是使用了 嵌套类可以访问外部类的静态属性和静态方法 的特性
private static class Holder {
private static Singleton3 instance = new Singleton3();
}
public static Singleton3 getInstance() {
return Holder.instance;
}
}
- 资源加载和性能:
- 饿汉模式在类创建的同时就实例化一个静态对象出来,不管之后会不会使用这个单例,都会占据一定的内存,但是相应的,在第一次调用时速度也会更快,因为其资源已经初始化完成
- 懒汉模式顾名思义,会延迟加载,在第一次使用该单例的时候才会实例化对象出来,第一次调用时要做初始化,如果要做的工作比较多,性能上会有些延迟,之后就和饿汉式一样了
单例模式琐碎知识点:
- 意图:保证一个类仅有一个实例,并提供一个访问它的全局访问点
- 主要解决:一个全局使用的类频繁地创建与销毁
- 何时使用:当您想控制实例数目,节省系统资源的时候
- 如何解决:判断系统是否已经有这个单例,如果有则返回,如果没有则创建
- 关键代码:构造函数是私有的
- 应用实例:
- 一个党只能有一个主席
- Windows 是多进程多线程的,在操作一个文件的时候,就不可避免地出现多个进程或线程同时操作一个文件的现象,所以所有文件的处理必须通过唯一的实例来进行
- 一些设备管理器常常设计为单例模式,比如一个电脑有两台打印机,在输出的时候就要处理不能两台打印机打印同一个文件。
- 优点:
- 在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例(比如管理学院首页页面缓存)
- 避免对资源的多重占用(比如写文件操作)。
- 缺点:没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化
- 使用场景:
- 要求生产唯一序列号。
- WEB 中的计数器,不用每次刷新都在数据库里加一次,用单例先缓存起来。
- 创建的一个对象需要消耗的资源过多,比如 I/O 与数据库的连接等。、
- 注意事项:getInstance() 方法中需要使用同步锁 synchronized (Singleton.class) 防止多线程同时进入造成instance 被多次实例化
5.建造者模式
经常碰见的 XxxBuilder 的类,通常都是建造者模式的产物。建造者模式其实有很多的变种,但是对于客户端来说,我们的使用通常都是一个模式的:
Food food = new FoodBuilder().a().b().c().build();
Food food = Food.builder().a().b().c().build();
- 先 new 一个 Builder,然后可以链式地调用一堆方法,最后再调用一次 build() 方法,我们需要的对象就有了
- 核心是:先把所有的属性都设置给 Builder,然后 build() 方法的时候,将这些属性复制给实际产生的对象
例:
class User {
// 下面是“一堆”的属性
private String name;
private String password;
private String nickName;
private int age;
// 构造方法私有化,不然客户端就会直接调用构造方法了
private User(String name, String password, String nickName, int age) {
this.name = name;
this.password = password;
this.nickName = nickName;
this.age = age;
}
// 静态方法,用于生成一个 Builder,这个不一定要有,不过写这个方法是一个很好的习惯,
// 有些代码要求别人写 new User.UserBuilder().a()...build() 看上去就没那么好
public static UserBuilder builder() {
return new UserBuilder();
}
public static class UserBuilder {
// 下面是和 User 一模一样的一堆属性
private String name;
private String password;
private String nickName;
private int age;
private UserBuilder() {
}
// 链式调用设置各个属性值,返回 this,即 UserBuilder
public UserBuilder name(String name) {
this.name = name;
return this;
}
public UserBuilder password(String password) {
this.password = password;
return this;
}
public UserBuilder nickName(String nickName) {
this.nickName = nickName;
return this;
}
public UserBuilder age(int age) {
this.age = age;
return this;
}
// build() 方法负责将 UserBuilder 中设置好的属性“复制”到 User 中。
// 当然,可以在 “复制” 之前做点检验
public User build() {
if (name == null || password == null) {
throw new RuntimeException("用户名和密码必填");
}
if (age <= 0 || age >= 150) {
throw new RuntimeException("年龄不合法");
}
// 还可以做赋予”默认值“的功能
if (nickName == null) {
nickName = name;
}
return new User(name, password, nickName, age);
}
}
}
客户端的调用:
public class APP {
public static void main(String[] args) {
User d = User.builder()
.name("foo")
.password("pAss12345")
.age(25)
.build();
}
}
建造者模式的链式写法很吸引人,但是,多写了很多“无用”的 builder 的代码,感觉这个模式没什么用。不过,当属性很多,而且有些必填,有些选填的时候,这个模式会使代码清晰很多。我们可以在 Builder 的构造方法中强制让调用者提供必填字段,还有,在 build() 方法中校验各个参数比在 User 的构造方法中校验,代码要优雅一些。
另:可以使用 lombok来简化建造者模式
当然,如果你只是想要链式写法,不想要建造者模式,有个很简单的办法,User 的 getter 方法不变,所有的 setter 方法都让其 return this 就可以了,然后就可以像下面这样调用:
User user = new User().setName("").setPassword("").setAge(20);
6.原型模式
- 有一个原型实例,基于这个原型实例产生新的实例,也就是“克隆”了
Object 类中有一个 clone() 方法,它用于生成一个新的对象,当然,如果我们要调用这个方法,java 要求我们的类必须先实现 Cloneable 接口,此接口没有定义任何方法,但是不这么做的话,在 clone() 的时候,会抛出 CloneNotSupportedException 异常
protected native Object clone() throws CloneNotSupportedException;
java 的克隆是浅克隆,碰到对象引用的时候,克隆出来的对象和原对象中的引用将指向同一个对象。通常实现深克隆的方法是将对象进行序列化,然后再进行反序列化
7.深克隆和浅克隆
浅克隆:
浅克隆(Shadow Clone)是把原型对象中成员变量为值类型的属性都复制给克隆对象,把原型对象中成员变量为引用类型的引用地址也复制给克隆对象,也就是原型对象中如果有成员变量为引用对象,则此引用对象的地址是共享给原型对象和克隆对象的。
即浅克隆碰到对象引用的时候,克隆出来的对象和原对象中的引用将指向同一个对象,如下图所示:
深克隆(Deep Clone)是将原型对象中的所有类型,无论是值类型还是引用类型,都复制一份给克隆对象,也就是说深克隆会把原型对象和原型对象所引用的对象,都复制一份给克隆对象,如下图所示:
在 Java 语言中要实现克隆则需要实现 Cloneable 接口,并重写 Object 类中的 clone() 方法,实现代码如下:
public class CloneExample {
public static void main(String[] args) throws CloneNotSupportedException {
// 创建被赋值对象
People p1 = new People();
p1.setId(1);
p1.setName("Java");
// 克隆 p1 对象
People p2 = (People) p1.clone();
// 打印名称
System.out.println("p2:" + p2.getName());
}
static class People implements Cloneable {
// 属性
private Integer id;
private String name;
/**
* 重写 clone 方法
* @throws CloneNotSupportedException
*/
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
}
//程序执行的结果为:
p2:Java
7.1 clone() 源码分析
/**
* Creates and returns a copy of this object. The precise meaning
* of "copy" may depend on the class of the object. The general
* intent is that, for any object {@code x}, the expression:
* <blockquote>
* <pre>
* x.clone() != x</pre></blockquote>
* will be true, and that the expression:
* <blockquote>
* <pre>
* x.clone().getClass() == x.getClass()</pre></blockquote>
* will be {@code true}, but these are not absolute requirements.
* While it is typically the case that:
* <blockquote>
* <pre>
* x.clone().equals(x)</pre></blockquote>
* will be {@code true}, this is not an absolute requirement.
* <p>
* By convention, the returned object should be obtained by calling
* {@code super.clone}. If a class and all of its superclasses (except
* {@code Object}) obey this convention, it will be the case that
* {@code x.clone().getClass() == x.getClass()}.
* <p>
* By convention, the object returned by this method should be independent
* of this object (which is being cloned). To achieve this independence,
* it may be necessary to modify one or more fields of the object returned
* by {@code super.clone} before returning it. Typically, this means
* copying any mutable objects that comprise the internal "deep structure"
* of the object being cloned and replacing the references to these
* objects with references to the copies. If a class contains only
* primitive fields or references to immutable objects, then it is usually
* the case that no fields in the object returned by {@code super.clone}
* need to be modified.
* <p>
* ......
*/
protected native Object clone() throws CloneNotSupportedException;
从以上源码的注释信息中我们可以看出,Object 对 clone() 方法的约定有三条:
- 对于所有对象来说,x.clone() !=x 应当返回 true,因为克隆对象与原对象不是同一个对象
- 对于所有对象来说,x.clone().getClass() == x.getClass() 应当返回 true,因为克隆对象与原对象的类型是一样的
- 对于所有对象来说,x.clone().equals(x) 应当返回 true,因为使用 equals 比较时,它们的值都是相同的
除了注释信息外,我们看 clone() 的实现方法,发现 clone() 是使用 native 修饰的本地方法,因此执行的性能会很高,并且它返回的类型为 Object,因此在调用克隆之后要把对象强转为目标类型才行
7.2 Arrays.copyOf()
如果是数组类型,可以直接使用 Arrays.copyOf() 来实现克隆,实现代码如下:
People[] o1 = {new People(1, "Java")};
People[] o2 = Arrays.copyOf(o1, o1.length);
// 修改原型对象的第一个元素的值
o1[0].setName("Jdk");
System.out.println("o1:" + o1[0].getName());
System.out.println("o2:" + o2[0].getName());
//以上程序的执行结果为:
nums1:[5, 5, 7, 9]
nums2:[5, 5, 7, 9]
从结果可以看出,我们在修改克隆对象的第一个元素之后,原型对象的第一个元素也跟着被修改了,这说明 Arrays.copyOf() 其实是一个浅克隆。
因为数组比较特殊数组本身就是引用类型,因此在使用 Arrays.copyOf() 其实只是把引用地址复制了一份给克隆对象,如果修改了它的引用对象,那么指向它的(引用地址)所有对象都会发生改变,因此看到的结果是,修改了克隆对象的第一个元素,原型对象也跟着被修改了.
7.3 深克隆实现方式汇总:
- 所有对象都实现克隆方法
- 通过构造方法实现深克隆
- 使用 JDK 自带的字节流实现深克隆
- 使用第三方工具实现深克隆,比如 Apache Commons Lang
- 使用 JSON 工具类实现深克隆,比如 Gson、FastJSON 等
接下来我们分别来实现以上这些方式,在开始之前先定义一个公共的用户类,代码如下:
- 可以看出在 People 对象中包含了一个引用对象 Address
/**
* 用户类
*/
public class People {
private Integer id;
private String name;
private Address address; // 包含 Address 引用对象
// 忽略构造方法、set、get 方法
}
/**
* 地址类
*/
public class Address {
private Integer id;
private String city;
// 忽略构造方法、set、get 方法
}
7.3.1 所有对象都实现克隆
这种方式我们需要修改 People 和 Address 类,让它们都实现 Cloneable 的接口,让所有的引用对象都实现克隆,从而实现 People 类的深克隆,代码如下:
public class CloneExample {
public static void main(String[] args) throws CloneNotSupportedException {
// 创建被赋值对象
Address address = new Address(110, "北京");
People p1 = new People(1, "Java", address);
// 克隆 p1 对象
People p2 = p1.clone();
// 修改原型对象
p1.getAddress().setCity("西安");
// 输出 p1 和 p2 地址信息
System.out.println("p1:" + p1.getAddress().getCity() +
" p2:" + p2.getAddress().getCity());
}
/**
* 用户类
*/
static class People implements Cloneable {
private Integer id;
private String name;
private Address address;
/**
* 重写 clone 方法
* @throws CloneNotSupportedException
*/
@Override
protected People clone() throws CloneNotSupportedException {
People people = (People) super.clone();
people.setAddress(this.address.clone()); // 引用类型克隆赋值
return people;
}
// 忽略构造方法、set、get 方法
}
/**
* 地址类
*/
static class Address implements Cloneable {
private Integer id;
private String city;
/**
* 重写 clone 方法
* @throws CloneNotSupportedException
*/
@Override
protected Address clone() throws CloneNotSupportedException {
return (Address) super.clone();
}
// 忽略构造方法、set、get 方法
}
}
//p1:西安 p2:北京
7.3.2 通过构造方法实现深克隆
《Effective Java》 中推荐使用构造器(Copy Constructor)来实现深克隆,如果构造器的参数为基本数据类型或字符串类型则直接赋值,如果是对象类型,则需要重新 new 一个对象,实现代码如下:
public class SecondExample {
public static void main(String[] args) throws CloneNotSupportedException {
// 创建对象
Address address = new Address(110, "北京");
People p1 = new People(1, "Java", address);
// 调用构造函数克隆对象
People p2 = new People(p1.getId(), p1.getName(),
new Address(p1.getAddress().getId(), p1.getAddress().getCity()));
// 修改原型对象
p1.getAddress().setCity("西安");
// 输出 p1 和 p2 地址信息
System.out.println("p1:" + p1.getAddress().getCity() +
" p2:" + p2.getAddress().getCity());
}
/**
* 用户类
*/
static class People {
private Integer id;
private String name;
private Address address;
// 忽略构造方法、set、get 方法
}
/**
* 地址类
*/
static class Address {
private Integer id;
private String city;
// 忽略构造方法、set、get 方法
}
}
//p1:西安 p2:北京
7.3.3 通过字节流实现深克隆
通过 JDK 自带的字节流实现深克隆的方式,是先将要原型对象写入到内存中的字节流,然后再从这个字节流中读出刚刚存储的信息,来作为一个新的对象返回,那么这个新对象和原型对象就不存在任何地址上的共享,这样就实现了深克隆,代码如下:
import java.io.*;
public class ThirdExample {
public static void main(String[] args) throws CloneNotSupportedException {
// 创建对象
Address address = new Address(110, "北京");
People p1 = new People(1, "Java", address);
// 通过字节流实现克隆
People p2 = (People) StreamClone.clone(p1);
// 修改原型对象
p1.getAddress().setCity("西安");
// 输出 p1 和 p2 地址信息
System.out.println("p1:" + p1.getAddress().getCity() +
" p2:" + p2.getAddress().getCity());
}
/**
* 通过字节流实现克隆
*/
static class StreamClone {
public static <T extends Serializable> T clone(People obj) {
T cloneObj = null;
try {
// 写入字节流
ByteArrayOutputStream bo = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bo);
oos.writeObject(obj);
oos.close();
// 分配内存,写入原始对象,生成新对象
ByteArrayInputStream bi = new ByteArrayInputStream(bo.toByteArray());
//获取上面的输出字节流
ObjectInputStream oi = new ObjectInputStream(bi);
// 返回生成的新对象
cloneObj = (T) oi.readObject();
oi.close();
} catch (Exception e) {
e.printStackTrace();
}
return cloneObj;
}
}
/**
* 用户类
*/
static class People implements Serializable {
private Integer id;
private String name;
private Address address;
// 忽略构造方法、set、get 方法
}
/**
* 地址类
*/
static class Address implements Serializable {
private Integer id;
private String city;
// 忽略构造方法、set、get 方法
}
}
//p1:西安 p2:北京
7.3.4 通过第三方工具实现深克隆
使用 Apache Commons Lang 来实现深克隆,实现代码如下:
import org.apache.commons.lang3.SerializationUtils;
import java.io.Serializable;
/**
* 深克隆实现方式四:通过 apache.commons.lang 实现
*/
public class FourthExample {
public static void main(String[] args) throws CloneNotSupportedException {
// 创建对象
Address address = new Address(110, "北京");
People p1 = new People(1, "Java", address);
// 调用 apache.commons.lang 克隆对象
People p2 = (People) SerializationUtils.clone(p1);
// 修改原型对象
p1.getAddress().setCity("西安");
// 输出 p1 和 p2 地址信息
System.out.println("p1:" + p1.getAddress().getCity() +
" p2:" + p2.getAddress().getCity());
}
/**
* 用户类
*/
static class People implements Serializable {
private Integer id;
private String name;
private Address address;
// 忽略构造方法、set、get 方法
}
/**
* 地址类
*/
static class Address implements Serializable {
private Integer id;
private String city;
// 忽略构造方法、set、get 方法
}
}
7.3.5 通过 JSON 工具类实现深克隆
使用 Google 提供的 JSON 转化工具 Gson 来实现,其他 JSON 转化工具类也是类似的,实现代码如下:
import com.google.gson.Gson;
/**
* 深克隆实现方式五:通过 JSON 工具实现
*/
public class FifthExample {
public static void main(String[] args) throws CloneNotSupportedException {
// 创建对象
Address address = new Address(110, "北京");
People p1 = new People(1, "Java", address);
// 调用 Gson 克隆对象
Gson gson = new Gson();
People p2 = gson.fromJson(gson.toJson(p1), People.class);
// 修改原型对象
p1.getAddress().setCity("西安");
// 输出 p1 和 p2 地址信息
System.out.println("p1:" + p1.getAddress().getCity() +
" p2:" + p2.getAddress().getCity());
}
/**
* 用户类
*/
static class People {
private Integer id;
private String name;
private Address address;
// 忽略构造方法、set、get 方法
}
/**
* 地址类
*/
static class Address {
private Integer id;
private String city;
// 忽略构造方法、set、get 方法
}
}
7.4 克隆设计理念猜想
对于克隆为什么要这样设计,官方没有直接给出答案,我们只能凭借一些经验和源码文档来试着回答一下这个问题。Java 中实现克隆需要两个主要的步骤:
- 实现 Cloneable 空接口
- 重写 Object 的 clone() 方法,再调用父类的克隆方法 (super.clone())
那为什么要这么做?
从源码中可以看出 Cloneable 接口诞生的比较早,JDK 1.0 就已经存在了,因此从那个时候就已经有克隆方法了,那我们怎么来标识一个类级别对象拥有克隆方法呢?克隆虽然重要,但我们不能给每个类都默认加上克隆,这显然是不合适的,那我们能使用的手段就只有这几个了:
- 在类上新增标识,此标识用于声明某个类拥有克隆的功能,像 final 关键字一样
- 使用 Java 中的注解
- 实现某个接口
- 继承某个类
先说第一个,为了一个重要但不常用的克隆功能, 单独新增一个类标识,这显然不合适;
再说第二个,因为克隆功能出现的比较早,那时候还没有注解功能,因此也不能使用;
第三点基本满足我们的需求。
第四点和第一点比较类似,为了一个克隆功能需要牺牲一个基类,并且 Java 只能单继承,因此这个方案也不合适。
采用排除法,无疑使用实现接口的方式是那时最合理的方案了,而且在 Java 语言中一个类可以实现多个接口。
那为什么要在 Object 中添加一个 clone() 方法呢?
因为 clone() 方法语义的特殊性,因此最好能有 JVM 的直接支持,既然要 JVM 直接支持,就要找一个 API 来把这个方法暴露出来才行,最直接的做法就是把它放入到一个所有类的基类 Object 中,这样所有类就可以很方便地调用到了。
8.总结
简单工厂模式最简单;工厂模式在简单工厂模式的基础上增加了选择工厂的维度,需要第一步选择合适的工厂;抽象工厂模式有产品族的概念,如果各个产品是存在兼容性问题的,就要用抽象工厂模式。单例模式就不说了,为了保证全局使用的是同一对象,一方面是安全性考虑,一方面是为了节省资源;建造者模式专门对付属性很多的那种类,为了让代码更优美;原型模式用得最少,了解和 Object 类中的 clone() 方法相关的知识即可
参考文章:
Java-Tutorial/初探Java设计模式1:创建型模式(工厂,单例等).md at master · h2pl/Java-Tutorial · GitHub
单例模式懒汉式和饿汉式区别_AH_HH的博客-CSDN博客_单例模式懒汉和饿汉的区别与实现
7.深克隆和浅克隆有什么区别?它的实现方式有哪些?_追梦忆影86的博客-CSDN博客_深克隆和浅克隆的区别