原型模式--- prototype

Posted 高高for 循环

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了原型模式--- prototype相关的知识,希望对你有一定的参考价值。

原型模式

定义;

原型模式属于对象创建模式,GOF 给它的定义为:用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。

Java中的原型模式:

在 Java 中提供了clone()方法来实现对象的克隆,所以Prototype 模式实现变得简单许 多。

实现条件:

  1. implements Cloneable接口
  2. 重写clone()方法

组成:


分类:

原型模式分为

  • 浅克隆
  • 深克隆

案例 1 :浅克隆

原型:Person

实现条件:

1. implements Cloneable接口
2. 重写clone()方法

class Person implements Cloneable {
    int age = 8;
    int score = 100;

    Location loc = new Location("北京", 22);

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

class Location {
    String street;
    int roomNo;

    @Override
    public String toString() {
        return "Location{" +
                "street='" + street + '\\'' +
                ", roomNo=" + roomNo +
                '}';
    }

    public Location(String street, int roomNo) {
        this.street = street;
        this.roomNo = roomNo;
    }
}

测试

public class Test01 {
    public static void main(String[] args) throws Exception {
        Person p1 = new Person();
        Person p2 = (Person)p1.clone();
        System.out.println(p2.age + " " + p2.score);
        System.out.println(p2.loc);
        System.out.println("---------------------------------");

        System.out.println("p1==p2: "+ (p1 == p2));
        System.out.println("p1.loc==p2.loc: "+(p1.loc == p2.loc));

        p1.loc.street = "上海";
        System.out.println("p1.loc:   "+p1.loc);
        System.out.println("p2.loc:   "+p2.loc);

    }

}

分析:

  1. 可以看出 p2是有p1克隆过去的,且p1和p2.地址值不同,但成员属性相同
  2. p1和p2的成员属性loc相同,都是指向堆中同一地址.即 p1.loc==p2.loc为true
  3. 所以当p1改变属性loc时,p2的属性loc也随之改变,这就是浅克隆

如果想克隆出来的p2不随p1改变而改变 ,则需要进行深克隆

案例 2 :深克隆

原型:Person

条件 :

Person 和Person的属性Location(引用数据类型)

都需要实现Cloneable接口和重写clone()方法

class Person implements Cloneable {
    int age = 8;
    int score = 100;

    Location loc = new Location("北京", 22);
    @Override
    public Object clone() throws CloneNotSupportedException {
        Person p = (Person)super.clone();
        p.loc = (Location)loc.clone();
        return p;
    }
}

class Location implements Cloneable {
    String street;
    int roomNo;

    public Location(String street, int roomNo) {
        this.street = street;
        this.roomNo = roomNo;
    }

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

    @Override
    public String toString() {
        return "Location{" +
                "street='" + street + '\\'' +
                ", roomNo=" + roomNo +
                '}';
    }

}

测试:


public class Test01 {
    public static void main(String[] args) throws Exception {
        Person p1 = new Person();
        Person p2 = (Person)p1.clone();
        System.out.println(p2.age + " " + p2.score);
        System.out.println(p2.loc);
        System.out.println("---------------------------------");

        System.out.println("p1==p2: "+ (p1 == p2));
        System.out.println("p1.loc==p2.loc: "+(p1.loc == p2.loc));

        p1.loc.street = "上海";
        System.out.println("p1.loc:   "+p1.loc);
        System.out.println("p2.loc:   "+p2.loc);

    }

}

分析: 可以看出p1改变自己的属性loc 时,p2并不会随之变化

案例 3 :关于string的克隆

情况1 ,如果是以字面量的方式定义的string

不需要再对string做克隆处理,就是深克隆模式

  • 因为通过字面量的方式(区别于new)给一个字符串赋值,此时的字符串值声明在字符串常量池中

package prototype;

public class Test02 {
    public static void main(String[] args) throws Exception {

        Person p1 = new Person();
        Person p2 = (Person) p1.clone();
        System.out.println("p1.loc == p2.loc? " + (p1.loc == p2.loc));
        System.out.println("p1.loc:   " + p1.loc);
        System.out.println("p2.loc:   " + p2.loc);

        System.out.println("------------------p1.loc.street 赋值为深圳------------------------------");

        p1.loc.street="深圳";
        System.out.println("p1.loc:   " + p1.loc);
        System.out.println("p2.loc:   " + p2.loc);
    }


}

class Person implements Cloneable {
    int age = 8;
    int score = 100;

    Location loc = new Location("上海", 22);
    @Override
    public Object clone() throws CloneNotSupportedException {
        Person p = (Person)super.clone();
        p.loc = (Location)loc.clone();
        return p;
    }
}

class Location implements Cloneable {
    String street;
    int roomNo;

    public Location(String street, int roomNo) {
        this.street = street;
        this.roomNo = roomNo;
    }

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

    @Override
    public String toString() {
        return "Location{" +
                "street='" + street + '\\'' +
                ", roomNo=" + roomNo +
                '}';
    }
}

  • 此时p2不会随着p1的变动而改变,属于深克隆

情况2 ,如果是以new string定义 或者 StringBuilder

不对string做克隆处理的话,则克隆出来的结果是浅克隆

class Person implements Cloneable {
    int age = 8;
    int score = 100;

    Location loc = new Location(new StringBuilder("上海"), 22);
    @Override
    public Object clone() throws CloneNotSupportedException {
        Person p = (Person)super.clone();
        p.loc = (Location)loc.clone();
        return p;
    }
}

class Location implements Cloneable {
    StringBuilder street;
    int roomNo;

    public Location(StringBuilder street, int roomNo) {
        this.street = street;
        this.roomNo = roomNo;
    }

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

    @Override
    public String toString() {
        return "Location{" +
                "street='" + street + '\\'' +
                ", roomNo=" + roomNo +
                '}';
    }
}

测试 :

此时 没对StringBuilder再做克隆处理

public class Test02 {
    public static void main(String[] args) throws Exception {

        Person p1 = new Person();
        Person p2 = (Person) p1.clone();
        System.out.println("p1.loc == p2.loc? " + (p1.loc == p2.loc));
        System.out.println("p1.loc:   " + p1.loc);
        System.out.println("p2.loc:   " + p2.loc);

        System.out.println("------------------p1.loc.street 字符串反转------------------------------");

        p1.loc.street.reverse();
        System.out.println("p1.loc:   " + p1.loc);
        System.out.println("p2.loc:   " + p2.loc);
    }


}

分析:

  1. 此时p2会随着p1的变动而改变,属于浅克隆
  2. 因为只是,StringBuilder是new出来的,不是字面量的方式定义,会在堆中开辟一块属于自己的空间

案例 4 : 序列化写法深克隆

浅复制:

  • 将一个对象复制后,基本数据类型的变量都会重新创建,而引用类型,指向的还是原对象所指向的。

深复制:

  • 将一个对象复制后,不论是基本数据类型还有引用类型,都是重新创建的。简单来说,就是深复制进行了完全彻底的复制,而浅复制不彻底。
public class Prototype implements Cloneable, Serializable {
 
	private static final long serialVersionUID = 1L;
	private String string;
 
	private SerializableObject obj;
 
	/* 浅复制 */
	public Object clone() throws CloneNotSupportedException {
		Prototype proto = (Prototype) super.clone();
		return proto;
	}
 
	/* 深复制 */
	public Object deepClone() throws IOException, ClassNotFoundException {
 
		/* 写入当前对象的二进制流 */
		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();
	}
 
	public String getString() {
		return string;
	}
 
	public void setString(String string) {
		this.string = string;
	}
 
	public SerializableObject getObj() {
		return obj;
	}
 
	public void setObj(SerializableObject obj) {
		this.obj = obj;
	}
 
}
 
class SerializableObject implements Serializable {
	private static final long serialVersionUID = 1L;
}
  • 方式二:通过对象的序列化实现

//方式二:通过对象的序列化实现
	public Object deepClone() {
		//创建流
		ByteArrayOutputStream bos = null;
		ObjectOutputStream oos = null;
		ByteArrayInputStream bis =null;
		ObjectInputStream ois = null;
		
		try {
			//序列化
			bos = new ByteArrayOutputStream();
			oos = new ObjectOutputStream(bos);
			oos.writeObject(this);//当前的对象以流的方式输出
			
			//反序列化
			bis = new ByteArrayInputStream(bos.toByteArray());
			ois = new ObjectInputStream(bis);
			DeepPrototype deepPrototype = (DeepPrototype)ois.readObject();
			
			return deepPrototype;
			
		} catch (Exception e) {
			// TODO: handle exception
			e.printStackTrace();
			return null;
		}finally {
			try {
				bos.close();
				oos.close();
				bis.close();
				ois.close();
			} catch (Exception e2) {
				// TODO: handle exception
			}
		}
		
	}

要实现深复制,需要采用流的形式读入当前对象的二进制输入,再写出二进制数据对应的对象。

原型模式在源码中的应用

ArrayList:

  • 实现Cloneable接口

  • 重写clone()方法

总结:

适用场景:

  1. 类初始化消耗资源较多时
  2. 初始化一个对象时需要非常繁琐的过程时
  3. 构造函数比较复杂时
  4. 循环体中生产大量对象时,可读性下降时

优点:

  1. 当创建一个对象比较复杂时,使用原型对象通常效率会更高
  2. 还有一个重要的用途就是保护性拷贝,也就是对某个对象对外可能是只读的,为了防止外部对这个只读对象的修改,通常可以通过返回一个对象拷贝的形式实现只读的限制。

缺点:

  1. 需要为每个对象配备克隆或者可拷贝的方法
  2. 对克隆复杂对象或者对克隆出来的对象进行复杂改造时,易带来风险,深克隆、浅克隆要运用得当
  3. 通过实现Cloneable接口的原型模式在调用clone函数构造实例时并不一定比通过new操作速度快,只有当通过new构造对象较为耗时或者说成本较高时,通过clone方法才能够获得效率上的提升

注意;

  1. 原型模式到底应该采用浅克隆还是深克隆?这个应根据实际业务场景具体分析
  2. 构造函数不会被执行,需格外注意。
  3. 由于 clone 方法在java 实现中有着一定的弊端和风险,所以clone 方法是不建议使用的。
    因此很少能在java 应用中看到原型模式的使用

以上是关于原型模式--- prototype的主要内容,如果未能解决你的问题,请参考以下文章

原型模式(Prototype)-创建型(Creational)设计模式

设计模式 - Prototype 原型模式

设计模式——10.原型模式

设计模式 - Prototype 原型模式

设计模式 - Prototype 原型模式

JAVA设计模式之原型模式(prototype)