1、原型设计模式

说明:

  • 用一个原型对象,创建和原型对象相同的对象,以能够保证创建对象的性能
  • 是创建大量相同对象的最佳方式

使用场景:

  • 对象的创建非常复杂,可以使用原型模式快捷的创建对象
  • 性能和安全要求比较高
@Data
public class Pig{

	private String name;   //名字
	private String doSomething;  //喜欢做的事
	
}

现在要表示佩奇一家,正常创建流程如下:

public class Test{

	public static void mian(STring[] args){
		Pig peki = new Pig();
		peki.setName("佩琪");
		peki.setDoSomething("喜欢吃蛋糕");
		System.out.println(peki);

		Pig george = new Pig();
		george.setName("乔治");
		george.setDoSomething("喜欢睡觉");
		System.out.println(george);

		Pig pigDad = new Pig();
		pigDad.setName("猪爸爸");
		pigDad.setDoSomething("喜欢开车");
		System.out.println(pigDad);

		Pig pigMum = new Pig();
		pigMum.setName("猪妈妈");
		pigMum.setDoSomething("喜欢做饭");
		System.out.println(pigMum);
	}
}

运行:

【Java设计模式】四、原型设计模式-LMLPHP

采用原型设计模式后:实体类实现Cloneable接口

@Data
public class Pig implements Cloneable{

	public Pig() {
		System.out.println("小猪被初始化了...");
	}
	private String name;   //名字
	private String doSomething;  //喜欢做的事
	
	@Override   //重写Object 的clone方法
	protected Object clone() throws CloneNotSupportedException{
		return super.clone();
	}
}

再次创建佩奇一家:

public class Test{

	public static void mian(STring[] args){
		Pig peki = new Pig();    //先new一个
		peki.setName("佩琪");
		peki.setDoSomething("喜欢吃蛋糕");
		System.out.println(peki);

		Pig george = (Pig) peki.clone();    //后面就克隆
		george.setName("乔治");    //如果这里不赋值,那克隆出来的属性和克隆样本一样
		george.setDoSomething("喜欢睡觉");
		System.out.println(george);

		Pig pigDad = (Pig) peki.clone() ;
		pigDad.setName("猪爸爸");
		pigDad.setDoSomething("喜欢开车");
		System.out.println(pigDad);

		Pig pigMum = (Pig) peki.clone() ;
		pigMum.setName("猪妈妈");
		pigMum.setDoSomething("喜欢做饭");
		System.out.println(pigMum);
	}
}

运行:

【Java设计模式】四、原型设计模式-LMLPHP

发现构造方法只被调用了一次,且出来的也照样是不同的对象因此,当对象属性很多,而又要创建大量这种对象时,就可以用原型设计模式。该模式产生的对象,虽然都是不同的对象,但如果不重新赋值,属性却是与克隆样本保持一致的,即使是一个新的对象。

到这儿有个想法:既然主要是为了方便批量创建对象,不用重复set一些属性。那你把一样的属性定义成static不是更好吗?那相比克隆就只是多调了一次无参构造。但其实克隆有自己的场景,比如一批一批的对象的一些属性是相同的:全市竞赛,同一学校的三好学生,其属性只有名字不一样,但各个学校来看属性就都不一样,此时你Student类的属性不能定义成static吧。

2、深克隆和浅克隆

主要针对对象中有引用类型属性,如:

public class Person{

	private String name;

	private Parent parent;   //父母亲
}
  • 浅克隆:克隆的新对象,其引用属性依然指向原来对象的对应属性的内存地址,即Parent属性的值指向同一个内存地址
  • 深克隆:引用属性的对象也会被克隆,不再指向原有的地址

Java中的Object类中提供了 clone() 方法来实现浅克隆。想要深克隆,可以采用对象流(序列化和反序列化)。案例:奖状类,有一个引用属性Student:

//奖状类
public class Citation implements Cloneable {
    private Student stu;

    public Student getStu() {
        return stu;
    }

    public void setStu(Student stu) {
        this.stu = stu;
    }

    void show() {
        System.out.println(stu.getName() + "同学:在2020学年第一学期中表现优秀,被评为三好学生。特发此状!");
    }

    @Override
    public Citation clone() throws CloneNotSupportedException {
        return (Citation) super.clone();
    }
}




学生类:

//学生类
@Data
@AllArgsConstructor
public class Student {
    private String name;
    private String address;
}

模拟了一个类有引用属性Student时的克隆:

//测试类
public class CitationTest {
    public static void main(String[] args) throws CloneNotSupportedException {

        Citation c1 = new Citation();
        Student stu = new Student("张三", "西安");
        c1.setStu(stu);

        //复制奖状
        Citation c2 = c1.clone();
        //获取c2奖状所属学生对象
        Student stu1 = c2.getStu();
        stu1.setName("李四");

        //判断stu对象和stu1对象是否是同一个对象
        System.out.println("stu和stu1是同一个对象?" + (stu == stu1));

        c1.show();
        c2.show();
    }
}

运行:

【Java设计模式】四、原型设计模式-LMLPHP

注意,默认的浅克隆下,引用属性指向的对象是同一个,后面set的引用属性的值,自然是会覆盖前面set克隆对象的引用属性值。使用对象流进行深克隆:

public class CitationTest1 {
    public static void main(String[] args) throws Exception {
        Citation c1 = new Citation();
        Student stu = new Student("张三", "西安");
        c1.setStu(stu);

        //创建对象输出流对象
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("C:\\Users\\Desktop\\b.txt"));
        //将c1对象写出到文件中
        oos.writeObject(c1);
        oos.close();

        //创建对象出入流对象
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("C:\\Users\\Desktop\\b.txt"));
        //读取对象
        Citation c2 = (Citation) ois.readObject();
        //获取c2奖状所属学生对象
        Student stu1 = c2.getStu();
        stu1.setName("李四");

        //判断stu对象和stu1对象是否是同一个对象
        System.out.println("stu和stu1是同一个对象?" + (stu == stu1));   //false

        c1.show();   //张三
        c2.show();   //李四
    }
}
03-05 09:23