原型模式也是拷贝模式,就是在内存中拷贝原型对象来创建一个跟原型对象一模一样的新对象。原型模式的实质就是扩展使用clone方法。
为什么要这样做?
1、我们直接创建一个对象可能会消耗很多的资源,如果我们需要创建的对象跟已存在的某个对象基本一样,我们可以使用这个存在的对象拷贝一个新的对象,然后进行修改,这样效率会更高。
2、如果直接创建一个新的对象需要进行大量的初始化操作,并且需要创建的对象跟已存在的某个对象基本一样,我们可以使用这个存在的对象拷贝一个新的对象。
3、当我们需要传递某个对象给其他接口使用,但又不需要其他接口更改这个对象的内容,我们可以通过原型模式拷贝一个对象来传递出去。
Client : 客户端用户。
Prototype : 抽象类或者接口,声明具备clone能力。
ConcretePrototype : 具体的原型类.
public interface IPerson extends Cloneable {
}
import java.util.ArrayList;
public class Person implements IPerson {
private String name;
private int age;
private ArrayList<String> hobbies=new ArrayList<String>();
public Person(){
}
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 ArrayList<String> getHobbies() {
return hobbies;
}
public void setHobbies(ArrayList<String> hobbies) {
this.hobbies = hobbies;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", hobbies=" + hobbies +
'}';
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
import java.util.ArrayList;
public class Client {
public static void main(String[] args) throws CloneNotSupportedException{
Person p=new Person();
p.setAge(18);
p.setName("张三");
System.out.println(p);
Person p1;
p1 = (Person) p.clone();
System.out.println(p1);
p1.setName("李四");
System.out.println(p);
System.out.println(p1);
}
}
输出结果:
Person{name=’张三’, age=18}
Person{name=’张三’, age=18}
Person{name=’张三’, age=18}
Person{name=’李四’, age=18}
浅拷贝和深拷贝
假如我们将Client的代码修改如下:
import java.util.ArrayList;
public class Client {
public static void main(String[] args) throws CloneNotSupportedException{
Person p=new Person();
p.setAge(18);
p.setName("张三");
ArrayList <String> hobbies=new ArrayList<String>();
hobbies.add("篮球");
hobbies.add("编程");
hobbies.add("长跑");
p.setHobbies(hobbies);
System.out.println(p);
Person p1;
p1 = (Person) p.clone();
System.out.println(p1);
p1.setName("李四");
p1.getHobbies().add("游泳");
System.out.println(p);
System.out.println(p1);
}
}
输出结果如下:
Person{name=’张三’, age=18, hobbies=[篮球, 编程, 长跑]}
Person{name=’张三’, age=18, hobbies=[篮球, 编程, 长跑]}
Person{name=’张三’, age=18, hobbies=[篮球, 编程, 长跑, 游泳]}
Person{name=’李四’, age=18, hobbies=[篮球, 编程, 长跑, 游泳]}
我们可以看到修改拷贝对象里面的内容,但是原对象里面的内容也被跟着修改了。因为上面的拷贝仅仅是浅拷贝,原对象和拷贝对象的hobbies引用指向的是同一个对象,当我们通过拷贝对象的引用来修改hobbies内容的时候,其实修改的也是原对象hobbies的内容。那如何解决这个问题呢? 那就是采用深拷贝,即在拷贝对象时,对于引用型的字段也要采用拷贝的形式,而不是单纯引用的形式。
Person中的clone方法修改如下:
@Override
protected Object clone() throws CloneNotSupportedException {
Person person = (Person) super.clone();
person.hobbies = (ArrayList<String>) hobbies.clone();
return person;
}
有时候我们会更多的看到原型模式的另一种写法。
在clone函数里调用构造函数,构造函数的入参是该类对象。
@Override
public Object clone(){
return new Person(this);
}
在构造函数中完成拷贝逻辑
public Person(Person person){
this.name=person.name;
this.age=person.age;
this.hobbies= new ArrayList<String>(hobbies);
}
android中的原型模式
Bundle类,该类实现了Cloneable接口
public Object clone() {
return new Bundle(this);
}
public Bundle(Bundle b) {
super(b);
mHasFds = b.mHasFds;
mFdsKnown = b.mFdsKnown;
}
Intent类,该类也实现了Cloneable接口
@Override
public Object clone() {
return new Intent(this);
}
public Intent(Intent o) {
this.mAction = o.mAction;
this.mData = o.mData;
this.mType = o.mType;
this.mPackage = o.mPackage;
this.mComponent = o.mComponent;
this.mFlags = o.mFlags;
this.mContentUserHint = o.mContentUserHint;
if (o.mCategories != null) {
this.mCategories = new ArraySet<String>(o.mCategories);
}
if (o.mExtras != null) {
this.mExtras = new Bundle(o.mExtras);
}
if (o.mSourceBounds != null) {
this.mSourceBounds = new Rect(o.mSourceBounds);
}
if (o.mSelector != null) {
this.mSelector = new Intent(o.mSelector);
}
if (o.mClipData != null) {
this.mClipData = new ClipData(o.mClipData);
}
}
用法也显得十分简单,一旦我们要用的Intent与现有的一个Intent很多东西都是一样的,那我们就可以直接拷贝现有的Intent,再修改不同的地方,便可以直接使用。
Uri uri = Uri.parse("smsto:10086");
Intent shareIntent = new Intent(Intent.ACTION_SENDTO, uri);
shareIntent.putExtra("sms_body", "hello");
Intent intent = (Intent)shareIntent.clone() ;
startActivity(intent);
优点与缺点
优点
原型模式是在内存二进制流的拷贝,要比直接 new 一个对象性能好很多,特别是要在一个循环体内产生大量的对象时,原型模式可以更好地体现其优点。
缺点
这既是它的优点也是缺点,直接在内存中拷贝,构造函数是不会执行的,在实际开发当中应该注意这个潜在的问题。优点就是减少了约束,缺点也是减少了约束,需要大家在实际应用时考虑。