创建型设计模式
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了创建型设计模式相关的知识,希望对你有一定的参考价值。
创建型设计模式成员
- 工厂方法模式
- 抽象工厂模式
- 建造者模式
- 原型模式
- 单例模式
1. 简单工厂模式
1.1 定义
简单工厂模式(Simple Factory Pattern):它属于类创建型模式。在简单工厂模式中,可以根据参数的不同返回不同类的实例。简单工厂模式专门定义一个类来负责创建其他类的实例,被创建的实例通常都具有共同的父类
1.2 角色组成
- Factory:工厂角色
- Product:抽象产品角色
- ConcreteProduct:具体产品角色
1.2.1 工厂角色Factory
SimpleFactory提供getMilk(String name)方法, 只需要告诉简单工厂牛奶品牌名称, 就能获得对应的牛奶
package com.zhunongyun.spring.springpattern.factory.simple;
import com.zhunongyun.spring.springpattern.factory.Mengniu;
import com.zhunongyun.spring.springpattern.factory.Milk;
import com.zhunongyun.spring.springpattern.factory.Telunsu;
import com.zhunongyun.spring.springpattern.factory.Yili;
public class SimpleFactory {
public Milk getMilk(String name){
Milk milk = null;
switch (name) {
case "特仑苏":
milk = new Telunsu();
break;
case "伊利":
milk = new Yili();
break;
case "蒙牛":
milk = new Mengniu();
break;
default:
System.out.println("不能生产您所需的产品");
break;
}
return milk;
}
}
### 1.2.2 抽象产品角色Product
> 抽象产品类Milk, 定义产品的属性, 具体产品都需要实现其属性
package com.zhunongyun.spring.springpattern.factory;
public interface Milk {
/**
- 获取一个标准产品
- @return
*/
public String getName();
}
1.2.3 具体产品角色ConcreteProduct
具体产品类SanLu, YiLi, Telunsu是具体的产品, 需要实现抽象产品类Milk
YiLi: package com.zhunongyun.spring.springpattern.factory;
public class Yili implements Milk {br/>@Override
public String getName() {
return "伊利";
}
}
Telunsu:
package com.zhunongyun.spring.springpattern.factory;
public class Telunsu implements Milk {br/>@Override
public String getName() {
return "特仑苏";
}
}
SanLu:
package com.zhunongyun.spring.springpattern.factory;
public class Sanlu implements Milk{br/>@Override
public String getName() {
return "三鹿";
}
}
### 1.2.4 测试
package com.zhunongyun.spring.springpattern.factory.simple;
public class SimpleFactoryTest {
public static void main(String[] args) {
SimpleFactory factory = new SimpleFactory();
//把用户的需求告诉工厂,需要用户提供具体产品的名称等信息,用户把产品创建交给工厂处理
System.out.println(factory.getMilk("AAA"));
System.out.println(factory.getMilk("蒙牛"));
}
}
输出:
不能生产您所需的产品
null
[email protected]
## 1.3 总结
由工厂, 抽象产品, 具体产品三个角色组成,用户需要向工厂提供产品名称信息,创建其中的一款产品, 新增产品需要增加新的具体产品,工厂也需要修改
# 2. 工厂方法模式
## 2.1 定义
工厂模式方法(factory method),定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到子类
## 2.2 角色组成
* 抽象工厂
* 具体工厂
* 抽象产品
* 具体产品
### 2.2.1 抽象工厂
package com.zhunongyun.spring.springpattern.factory.func;
import com.zhunongyun.spring.springpattern.factory.Milk;
public interface Factory {
//工厂必然具有生产产品技能,统一的产品出口
Milk getMilk();
}
### 2.2.2 具体工厂
MengniuFactory:
package com.zhunongyun.spring.springpattern.factory.func;
import com.zhunongyun.spring.springpattern.factory.Mengniu;
import com.zhunongyun.spring.springpattern.factory.Milk;
public class MengniuFactory implements Factory {br/>@Override
public Milk getMilk() {
return new Mengniu();
}
}
SanluFactory:
package com.zhunongyun.spring.springpattern.factory.func;
import com.zhunongyun.spring.springpattern.factory.Milk;
import com.zhunongyun.spring.springpattern.factory.Sanlu;
public class SanluFactory implements Factory {br/>@Override
public Milk getMilk() {
return new Sanlu();
}
}
TelunsuFactory:
package com.zhunongyun.spring.springpattern.factory.func;
import com.zhunongyun.spring.springpattern.factory.Milk;
import com.zhunongyun.spring.springpattern.factory.Telunsu;
public class TelunsuFactory implements Factory {
@Override
public Milk getMilk() {
return new Telunsu();
}
}
### 2.2.3 抽象产品
package com.zhunongyun.spring.springpattern.factory;
public interface Milk {
/**
- 获取一个标准产品
- @return
*/
public String getName();
}
2.2.4 具体产品
YiLi:
package com.zhunongyun.spring.springpattern.factory;
public class Yili implements Milk {
@Override
public String getName() {
return "伊利";
}
}
Telunsu:
package com.zhunongyun.spring.springpattern.factory;
public class Telunsu implements Milk {
@Override
public String getName() {
return "特仑苏";
}
}
SanLu:
package com.zhunongyun.spring.springpattern.factory;
public class Sanlu implements Milk{
@Override
public String getName() {
return "三鹿";
}
}
2.2.5 测试
package com.zhunongyun.spring.springpattern.factory.func;
public class FactoryTest {
public static void main(String[] args) {
Factory factory = new SanluFactory();
System.out.println(factory.getMilk());
}
}
输出:
[email protected]
2.3 总结
在工厂方法模式下,如果要增加产品,只需要扩展对应的具体工厂(ConcreteCreator)和具体产品(ConcreteProduct)即可,原有源码无需改变,遵循了“开闭原则”
用户需要某款产品只需要实例化对应的产品,此时没有创建产品,只要当需要使用时才会创建产品,用户在使用时都是以方法的事情操作产品
3. 抽象工厂模式
3.1 定义
抽象工厂模式(Abstract Factory Pattern)是围绕一个超级工厂创建其他工厂。该超级工厂又称为其他工厂的工厂。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式
3.2 角色组成
- 抽象工厂
- 具体工厂
- 抽象产品
- 具体产品
3.2.1 抽象工厂
package com.zhunongyun.spring.springpattern.factory.abstr;
import com.zhunongyun.spring.springpattern.factory.Milk;
public abstract class AbstractFactory {
//公共的逻辑
//方便于统一管理
/**
* 获得一个蒙牛品牌的牛奶
* @return
*/
public abstract Milk getMengniu();
/**
* 获得一个伊利品牌的牛奶
* @return
*/
public abstract Milk getYili();
/**
* 获得一个特仑苏品牌的牛奶
* @return
*/
public abstract Milk getTelunsu();
public abstract Milk getSanlu();
}
3.2.2 具体工厂
package com.zhunongyun.spring.springpattern.factory.abstr;
import com.zhunongyun.spring.springpattern.factory.*;
public class MilkFactory extends AbstractFactory {
@Override
public Milk getMengniu() {
return new Mengniu();
}
@Override
public Milk getYili() {
return new Yili();
}
@Override
public Milk getTelunsu() {
return new Telunsu();
}
@Override
public Milk getSanlu() {
return new Sanlu();
}
}
3.3.3 测试
package com.zhunongyun.spring.springpattern.factory.abstr;
public class AbstractFactoryTest {
public static void main(String[] args) {
MilkFactory factory = new MilkFactory();
//对于用户而言,更加简单了
//用户只有选择的权利了,保证了程序的健壮性
System.out.println(factory.getSanlu());
}
}
输出:
[email protected]
3.4 总结
抽象工厂模式,定义了一个抽象工厂,抽象工厂中定义了创建对象的接口,由实现类实现对象创建,用户在使用时,只需要实例化抽象工厂,就能通过接口获取对象
4. 单例模式
4.1 定义
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。保证一个类仅有一个实例,并提供一个访问它的全局访问点
4.2 单例模式的几种实现方式
4.2.1 懒汉式
这种方式是lazy初始化,第一次调用才初始化,避免内存浪费, 多线程时为了保证线程安全需要加锁
线程不安全:
package com.zhunongyun.spring.springpattern.singleton;
public class Lazy {
private static Lazy instance;
private Lazy (){}
public static Lazy getInstance() {
if (instance == null) {
instance = new Lazy();
}
return instance;
}
}
线程安全:
package com.zhunongyun.spring.springpattern.singleton;
public class Lazy {
private static Lazy instance;
private Lazy (){}
public static synchronized Lazy getInstance() {
if (instance == null) {
instance = new Lazy();
}
return instance;
}
}
4.2.2 饿汉式
这种方式,类加载时就初始化,浪费内存,基于classloader机制避免了多线程的同步问题, 多线程下是安全的
package com.zhunongyun.spring.springpattern.singleton;
public class Hungry {
private static Hungry instance = new Hungry();
private Hungry(){}
public static Hungry getInstance() {
return instance;
}
}
4.2.3 双重校验锁
这种方式采用双锁机制,安全且在多线程情况下能保持高性能
package com.zhunongyun.spring.springpattern.singleton;
public class DoubleCheckedLocking {
private volatile static DoubleCheckedLocking singleton;
private DoubleCheckedLocking() {
}
public static DoubleCheckedLocking getSingleton() {
if (singleton == null) {
synchronized (DoubleCheckedLocking.class) {
if (singleton == null) {
singleton = new DoubleCheckedLocking();
}
}
}
return singleton;
}
}
4.2.4 登记式/静态内部类
这种方式能达到双检锁方式一样的功效,但实现更简单。对静态域使用延迟初始化,应使用这种方式而不是双检锁方式。这种方式只适用于静态域的情况,双检锁方式可在实例域需要延迟初始化时使用
这种方式同样利用了classloader机制来保证初始化instance时只有一个线程,SingletonHolder类没有被主动使用,只有通过显式调用getInstance方法时,才会显式装载SingletonHolder类,从而实例化instance
package com.zhunongyun.spring.springpattern.singleton;
public class Register {
private static class SingletonHolder {
private static final Register INSTANCE = new Register();
}
private Register() {
}
public static final Register getInstance() {
return SingletonHolder.INSTANCE;
}
}
4.2.5 枚举
这种实现方式还没有被广泛采用,但这是实现单例模式的最佳方法。它更简洁,自动支持序列化机制,绝对防止多次实例化,它不仅能避免多线程同步问题,而且还自动支持序列化机制,防止反序列化重新创建新的对象,绝对防止多次实例化
package com.zhunongyun.spring.springpattern.singleton;
/**
* 数据库连接
*/
public class DBConnection {
}
package com.zhunongyun.spring.springpattern.singleton;
public enum DataSourceEnum {
DATASOURCE;
private DBConnection connection = null;
private DataSourceEnum() {
connection = new DBConnection();
}
public DBConnection getConnection() {
return connection;
}
}
package com.zhunongyun.spring.springpattern.singleton;
public class DataSourceEnumTest {
public static void main(String[] args) {
DBConnection con1 = DataSourceEnum.DATASOURCE.getConnection();
DBConnection con2 = DataSourceEnum.DATASOURCE.getConnection();
System.out.println(con1 == con2);
}
}
输出:
true
结果表明两次获取返回了相同的实例
Java规范中规定,每一个枚举类型极其定义的枚举变量在JVM中都是唯一的,因此在枚举类型的序列化和反序列化上,Java做了特殊的规定
在序列化的时候Java仅仅是将枚举对象的name属性输出到结果中,反序列化的时候则是通过 java.lang.Enum 的valueOf()方法来根据名字查找枚举对象
也就是说,以下面枚举为例,序列化的时候只将DATASOURCE这个名称输出,反序列化的时候再通过这个名称,查找对于的枚举类型,因此反序列化后的实例也会和之前被序列化的对象实例相同
枚举会是线程安全、序列化与反序列化是相同的实例
4.3 总结
一般情况下,不建议使用4.2.1懒汉方式,建议使用4.2.2饿汉方式。只有在要明确实现lazy loading效果时,才会使用4.2.4登记方式。如果涉及到反序列化创建对象时,可以尝试使用4.2.5枚举方式。如果有其他特殊的需求,可以考虑使用4.2.3双检锁方式
5. 原型模式
5.1 定义
原型模式(Prototype Pattern)是用于创建重复的对象,同时又能保证性能。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
这种模式是实现了一个原型接口,该接口用于创建当前对象的克隆。当直接创建对象的代价比较大时,则采用这种模式。例如,一个对象需要在一个高代价的数据库操作之后被创建。我们可以缓存该对象,在下一个请求时返回它的克隆,在需要的时候更新数据库,以此来减少数据库调用
5.2 序列化和反序列化
5.2.1 序列化和反序列化的概念
- 序列化: 把对象转换为字节序列的过程称为对象的序列化
- 反序列化: 把字节序列恢复为对象的过程称为对象的反序列化
对象的序列化主要有两种用途:
- 1 把对象的字节序列永久地保存到硬盘上,通常存放在一个文件中;
- 2 在网络上传送对象的字节序列。
当两个进程在进行远程通信时,彼此可以发送各种类型的数据。无论是何种类型的数据,都会以二进制序列的形式在网络上传送。发送方需要把这个Java对象转换为字节序列,才能在网络上传送;接收方则需要把字节序列再恢复为Java对象
5.2.2 Serializable接口类
在JDK库中只有实现了Serializable和Externalizable接口的类的对象才能被序列化。Externalizable接口继承自Serializable接口,实现Externalizable接口的类完全由自身来控制序列化的行为,而仅实现Serializable接口的类可以采用默认的序列化方式
package com.zhunongyun.spring.prototype;
import lombok.Data;
import java.io.Serializable;
/**
* 测试对象序列化和反序列化
*/
@Data
public class Person implements Serializable {
/**
* 序列化ID
*/
private static final long serialVersionUID = -5809782578272943999L;
private int age;
private String name;
private String sex;
}
5.2.3 serialVersionUID的作用
serialVersionUID字面意思上是序列化的版本号,凡是实现Serializable接口的类都有一个表示序列化版本标识符的静态变量
private static final long serialVersionUID = -5809782578272943999L;
两种生成方式:
- 1 default serial version ID
- 2 generated serial version ID
// default serial version ID
private static final long serialVersionUID = 1L;
// generated serial version ID
private static final long serialVersionUID = 4603642343377807741L;
那么serialVersionUID(序列化版本号)到底有什么用呢,我们用如下的例子来说明一下serialVersionUID的作用,看下面的代码:
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
public class TestSerialversionUID {
public static void main(String[] args) throws Exception {
SerializeCustomer();// 序列化Customer对象
Customer customer = DeserializeCustomer();// 反序列Customer对象
System.out.println(customer);
}
/**
* MethodName: SerializeCustomer
* Description: 序列化Customer对象
* @author xudp
* @throws FileNotFoundException
* @throws IOException
*/
private static void SerializeCustomer() throws FileNotFoundException,
IOException {
Customer customer = new Customer("gacl",25);
// ObjectOutputStream 对象输出流
ObjectOutputStream oo = new ObjectOutputStream(new FileOutputStream(
new File("E:/Customer.txt")));
oo.writeObject(customer);
System.out.println("Customer对象序列化成功!");
oo.close();
}
/**
* MethodName: DeserializeCustomer
* Description: 反序列Customer对象
* @author xudp
* @return
* @throws Exception
* @throws IOException
*/
private static Customer DeserializeCustomer() throws Exception, IOException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(
new File("E:/Customer.txt")));
Customer customer = (Customer) ois.readObject();
System.out.println("Customer对象反序列化成功!");
return customer;
}
}
/**
* <p>ClassName: Customer<p>
* <p>Description: Customer实现了Serializable接口,可以被序列化<p>
* @author xudp
* @version 1.0 V
* @createTime 2014-6-9 下午04:20:17
*/
class Customer implements Serializable {
//Customer类中没有定义serialVersionUID
private String name;
private int age;
public Customer(String name, int age) {
this.name = name;
this.age = age;
}
/*
* @MethodName toString
* @Description 重写Object类的toString()方法
* @author xudp
* @return string
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return "name=" + name + ", age=" + age;
}
}
运行结果:
下面我们修改一下Customer类,添加多一个sex属性,如下:
class Customer implements Serializable {
//Customer类中没有定义serialVersionUID
private String name;
private int age;
//新添加的sex属性
private String sex;
public Customer(String name, int age) {
this.name = name;
this.age = age;
}
public Customer(String name, int age,String sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
/*
* @MethodName toString
* @Description 重写Object类的toString()方法
* @author xudp
* @return string
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return "name=" + name + ", age=" + age;
}
}
然后执行反序列操作,此时就会抛出如下的异常信息:
Exception in thread "main" java.io.InvalidClassException: Customer;
local class incompatible:
stream classdesc serialVersionUID = -88175599799432325,
local class serialVersionUID = -5182532647273106745
意思就是说,文件流中的class和classpath中的class,也就是修改过后的class,不兼容了,处于安全机制考虑,程序抛出了错误,并且拒绝载入。那么如果我们真的有需求要在序列化后添加一个字段或者方法呢?应该怎么办?那就是自己去指定serialVersionUID。在TestSerialversionUID例子中,没有指定Customer类的serialVersionUID的,那么java编译器会自动给这个class进行一个摘要算法,类似于指纹算法,只要这个文件多一个空格,得到的UID就会截然不同的,可以保证在这么多类中,这个编号是唯一的。所以,添加了一个字段后,由于没有显指定serialVersionUID,编译器又为我们生成了一个UID,当然和前面保存在文件中的那个不会一样了,于是就出现了2个序列化版本号不一致的错误。因此,只要我们自己指定了serialVersionUID,就可以在序列化后,去添加一个字段,或者方法,而不会影响到后期的还原,还原后的对象照样可以使用,而且还多了方法或者属性可以用。
下面继续修改Customer类,给Customer指定一个serialVersionUID,修改后的代码如下:
class Customer implements Serializable {
/**
* Customer类中定义的serialVersionUID(序列化版本号)
*/
private static final long serialVersionUID = -5182532647273106745L;
private String name;
private int age;
//新添加的sex属性
//private String sex;
public Customer(String name, int age) {
this.name = name;
this.age = age;
}
/*public Customer(String name, int age,String sex) {
this.name = name;
this.age = age;
this.sex = sex;
}*/
/*
* @MethodName toString
* @Description 重写Object类的toString()方法
* @author xudp
* @return string
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return "name=" + name + ", age=" + age;
}
}
重新执行序列化操作,将Customer对象序列化到本地硬盘的Customer.txt文件存储,然后修改Customer类,添加sex属性,修改后的Customer类代码如下:
class Customer implements Serializable {
/**
* Customer类中定义的serialVersionUID(序列化版本号)
*/
private static final long serialVersionUID = -5182532647273106745L;
private String name;
private int age;
//新添加的sex属性
private String sex;
public Customer(String name, int age) {
this.name = name;
this.age = age;
}
public Customer(String name, int age,String sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
/*
* @MethodName toString
* @Description 重写Object类的toString()方法
* @author xudp
* @return string
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return "name=" + name + ", age=" + age;
}
}
执行反序列操作,这次就可以反序列成功了,如下所示:
5.2.4 serialVersionUID的取值
serialVersionUID的取值是Java运行时环境根据类的内部细节自动生成的。如果对类的源代码作了修改,再重新编译,新生成的类文件的serialVersionUID的取值有可能也会发生变化。
类的serialVersionUID的默认值完全依赖于Java编译器的实现,对于同一个类,用不同的Java编译器编译,有可能会导致不同的serialVersionUID,也有可能相同。为了提高serialVersionUID的独立性和确定性,强烈建议在一个可序列化类中显示的定义serialVersionUID,为它赋予明确的值。
显式地定义serialVersionUID有两种用途:
1、 在某些场合,希望类的不同版本对序列化兼容,因此需要确保类的不同版本具有相同的serialVersionUID;
2、 在某些场合,不希望类的不同版本对序列化兼容,因此需要确保类的不同版本具有不同的serialVersionUID。
5.3 深复制与浅复制
5.3.1 拷贝的引入
5.3.1.1 引用拷贝
创建一个指向对象的引用变量的拷贝。
Teacher teacher = new Teacher("Taylor",26);
Teacher otherteacher = teacher;
System.out.println(teacher);
System.out.println(otherteacher);
输出结果:
[email protected]
[email protected]
结果分析:由输出结果可以看出,它们的地址值是相同的,那么它们肯定是同一个对象。teacher和otherteacher的只是引用而已,他们都指向了一个相同的对象Teacher(“Taylor”,26)。 这就叫做引用拷贝。
5.3.1.2 对象拷贝
创建对象本身的一个副本。
Teacher teacher = new Teacher("Swift",26);
Teacher otherteacher = (Teacher)teacher.clone();
System.out.println(teacher);
System.out.println(otherteacher);
输出结果:
[email protected]
[email protected]
结果分析:由输出结果可以看出,它们的地址是不同的,也就是说创建了新的对象, 而不是把原对象的地址赋给了一个新的引用变量,这就叫做对象拷贝。
深拷贝和浅拷贝都是对象拷贝
5.3.2 浅拷贝
5.3.2.1 定义:
被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象。即对象的浅拷贝会对“主”对象进行拷贝,但不会复制主对象里面的对象。”里面的对象“会在原来的对象和它的副本之间共享。
简而言之,浅拷贝仅仅复制所考虑的对象,而不复制它所引用的对象
5.3.2.2 浅拷贝实例
package blog;
/**
* Created by 白夜行 on 2017/5/8.
*/
public class ShallowCopy {
public static void main(String[] args) throws CloneNotSupportedException {
Teacher teacher = new Teacher();
teacher.setName("Delacey");
teacher.setAge(29);
Student2 student1 = new Student2();
student1.setName("Dream");
student1.setAge(18);
student1.setTeacher(teacher);
Student2 student2 = (Student2) student1.clone();
System.out.println("拷贝后");
System.out.println(student2.getName());
System.out.println(student2.getAge());
System.out.println(student2.getTeacher().getName());
System.out.println(student2.getTeacher().getAge());
System.out.println("修改老师的信息后-------------");
// 修改老师的信息
teacher.setName("Jam");
System.out.println(student1.getTeacher().getName());
System.out.println(student2.getTeacher().getName());
}
}
class Teacher implements Cloneable
{
private String name;
private int age;
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
public int getAge()
{
return age;
}
public void setAge(int age)
{
this.age = age;
}
}
class Student2 implements Cloneable
{
private String name;
private int age;
private Teacher teacher;
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
public int getAge()
{
return age;
}
public void setAge(int age)
{
this.age = age;
}
public Teacher getTeacher()
{
return teacher;
}
public void setTeacher(Teacher teacher)
{
this.teacher = teacher;
}
@Override
public Object clone() throws CloneNotSupportedException
{
Object object = super.clone();
return object;
}
}
输出结果:
拷贝后
Dream
18
Delacey
29
修改老师的信息后-------------
Jam
Jam
结果分析: 两个引用student1和student2指向不同的两个对象,但是两个引用student1和student2中的两个teacher引用指向的是同一个对象,所以说明是浅拷贝。
5.3.3 .深拷贝
5.3.3.1 定义
深拷贝是一个整个独立的对象拷贝,深拷贝会拷贝所有的属性,并拷贝属性指向的动态分配的内存。当对象和它所引用的对象一起拷贝时即发生深拷贝。深拷贝相比于浅拷贝速度较慢并且花销较大。
简而言之,深拷贝把要复制的对象所引用的对象都复制了一遍。
5.3.3.2 实现深拷贝(实例1)
package blog;
/**
* Created by 白夜行 on 2017/5/8.
*/
public class DeepCopy {
public static void main(String[] args) throws Exception
{
Teacher2 teacher = new Teacher2();
teacher.setName("Delacey");
teacher.setAge(29);
Student3 student1 = new Student3();
student1.setName("Dream");
student1.setAge(18);
student1.setTeacher(teacher);
Student3 student2 = (Student3) student1.clone();
System.out.println("拷贝后");
System.out.println(student2.getName());
System.out.println(student2.getAge());
System.out.println(student2.getTeacher().getName());
System.out.println(student2.getTeacher().getAge());
System.out.println("修改老师的信息后-------------");
// 修改老师的信息
teacher.setName("Jam");
System.out.println(student1.getTeacher().getName());
System.out.println(student2.getTeacher().getName());
}
}
class Teacher2 implements Cloneable {
private String name;
private int age;
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
public int getAge()
{
return age;
}
public void setAge(int age)
{
this.age = age;
}
@Override
public Object clone() throws CloneNotSupportedException
{
return super.clone();
}
}
class Student3 implements Cloneable {
private String name;
private int age;
private Teacher2 teacher;
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
public int getAge()
{
return age;
}
public void setAge(int age)
{
this.age = age;
}
public Teacher2 getTeacher()
{
return teacher;
}
public void setTeacher(Teacher2 teacher)
{
this.teacher = teacher;
}
@Override
public Object clone() throws CloneNotSupportedException
{
// 浅复制时:
// Object object = super.clone();
// return object;
// 改为深复制:
Student3 student = (Student3) super.clone();
// 本来是浅复制,现在将Teacher对象复制一份并重新set进来
student.setTeacher((Teacher2) student.getTeacher().clone());
return student;
}
}
输出结果:
拷贝后
Dream
18
Delacey
29
修改老师的信息后-------------
Jam
Delacey
结果分析:
两个引用student1和student2指向不同的两个对象,两个引用student1和student2中的两个teacher引用指向的是两个对象,但对teacher对象的修改只能影响student1对象,所以说是深拷贝
teacher姓名Delacey更改前:
teacher姓名Jam更改后:
5.3.3.3 利用序列化实现深拷贝
package blog;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
/**
* Created by 白夜行 on 2017/5/13.
*/
public class DeepCopyServiable {
public static void main(String[] args) throws Exception {
Teacher3 t = new Teacher3();
t.setName("Taylor");
t.setAge(28);
Student3 s1 = new Student3();
s1.setAge(20);
s1.setName("blank space");
s1.setTeacher(t);
Student3 s2 = (Student3) s1.deepClone();
System.out.println("拷贝后:");
System.out.println(s2.getName());
System.out.println(s2.getAge());
System.out.println(s2.getTeacher().getName());
System.out.println(s2.getTeacher().getAge());
System.out.println("---------------------------");
t.setName("swift");
System.out.println("修改后:");
System.out.println(s1.getTeacher().getName());
System.out.println(s2.getTeacher().getName());
}
}
class Teacher3 implements Serializable
{
private String name;
private int age;
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
public int getAge()
{
return age;
}
public void setAge(int age)
{
this.age = age;
}
}
class Student3 implements Serializable
{
private String name;
private int age;
private Teacher3 teacher;
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
public int getAge()
{
return age;
}
public void setAge(int age)
{
this.age = age;
}
public Teacher3 getTeacher()
{
return teacher;
}
public void setTeacher(Teacher3 teacher)
{
this.teacher = teacher;
}
public Object deepClone() throws Exception
{
// 序列化
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(this);
// 反序列化
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
return ois.readObject();
}
}
输出结果:
拷贝后:
blank space
20
Taylor
28
---------------------------
修改后:
swift
Taylor
结果分析:说明用序列化的方式实现了对象的深拷贝
6. 建造者模式
6.1 定义
以上是关于创建型设计模式的主要内容,如果未能解决你的问题,请参考以下文章