您的位置:首页 > 其它

原型设计模型(深拷贝浅拷贝)

2016-03-23 23:01 381 查看

一.使用背景

(1)类初始化需要消化非常多的资源,这个资源包括数据,硬件资源,通过原型拷贝可以避免这些消耗。

(2)通过new产生一个对象需要非常繁琐的数据准备或访问权限,这时可以使用原型模式

(3)一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时,可以考虑使用原型模式拷贝多个对象供调用者使用,即保护性拷贝。(避免别人改变对象的值,但是可以使用它的值)

注意:

<1>.通过cloneable接口的原型模式在调用clone函数构造实例时并不一定比通过new操作速度快,只有通过new构造对象较为耗时或者成本比较高时,通过clone方法才能够获得效率上的提升,因此,在使用cloneable时需要考虑构建对象的成本以及做一些效率上的尝试。

<2>.实现原型模式也不一定非要实现Cloneable接口,也有其他的实现方式

 

二.角色

Client:客户端用户

Prototype:抽象类或者接口,声明具备clone能力

ConcretePrototype:具体的原型类。

 

//client.java  

/**
* Created by Administrator on 2016/3/13 0013.
*/
public class Client {
public static void main(String[] args) throws CloneNotSupportedException {
//1.构建文档对象
WordDocument originDoc = new WordDocument();
//2.编辑文档,添加图片等
originDoc.setmText("这是一篇文档");
originDoc.addmImages("图片1");
originDoc.addmImages("图片2");
originDoc.addmImages("图片3");
originDoc.showDocument(); //原始打印

//以原始文档为原型,拷贝一份副本
WordDocument doc2 = (WordDocument)originDoc.clone();
doc2.showDocument(); //拷贝的打印
//修改文档副本,不会影响原始文档
doc2.setmText("这是修改过的Doc2文本");
doc2.showDocument(); //拷贝的打印
originDoc.showDocument(); //原始打印

}
}
//WordDocument.java

 

import java.util.ArrayList;

/**
* Created by Administrator on 2016/3/13 0013.
*/
public class WordDocument implements Cloneable {
//文本
private String mText;
//图片名列表
private ArrayList<String> mImages = new ArrayList<>();
public WordDocument(){
System.out.println("------WordDocument-----");
}

//clone这个方法是Object中的,如果没有Cloneable,却调用这个方法会报异常!!!
@Override
protected Object clone() throws CloneNotSupportedException {
try{
WordDocument doc = (WordDocument)super.clone();
doc.mText = this.mText;
doc.mImages = this.mImages;
return doc;
}catch(Exception e){
return null;
}
}

public String getmText() {
return mText;
}

public void setmText(String mText) {
this.mText = mText;
}

public ArrayList<String> getmImages() {
return mImages;
}

public void addmImages(String img) {
this.mImages.add(img);
}

public void showDocument(){
System.out.println("--------word content start --------");
System.out.println("Text :"+mText);
System.out.println("images List : ");
for (String imgName : mImages){
System.out.println("image name :"+ imgName);
}
System.out.println("--------word content End --------");
}
}
//打印结果

------WordDocument-----

--------word content start --------

Text :这是一篇文档

images List : 

image name :图片1

image name :图片2

image name :图片3

--------word content End --------

--------word content start --------

Text :这是一篇文档

images List : 

image name :图片1

image name :图片2

image name :图片3

--------word content End --------

--------word content start --------

Text :这是修改过的Doc2文本

images List : 

image name :图片1

image name :图片2

image name :图片3

--------word content End --------

--------word content start --------

Text :这是一篇文档

images List : 

image name :图片1

image name :图片2

image name :图片3

--------word content End --------

 

 

从上述的结果可以看出  doc2是通过originDoc.clone创建的

然后doc2修改了文本内容以后并不会影响originDoc的文本内容,这就保证了orginDoc的安全性。还要注意,通过clone拷贝对象时,并不会执行构造函数!

 三.浅拷贝和深拷贝

上面的例子是浅拷贝,也叫影子拷贝,这份拷贝实际上并不是将原始文档的所有字段都重新构造了一份,而是副本文档的字段引用原始文档的字段。

 

 

也就是说如果A引用B就是说两个对象指向同一个地址,当修改A时B也会改变,B修改时A同样改变。

//Client.java(修改了一条)

/**
* Created by Administrator on 2016/3/13 0013.
*/
public class Client {
public static void main(String[] args) throws CloneNotSupportedException {
//1.构建文档对象
WordDocument originDoc = new WordDocument();
//2.编辑文档,添加图片等
originDoc.setmText("这是一篇文档");
originDoc.addmImages("图片1");
originDoc.addmImages("图片2");
originDoc.addmImages("图片3");
originDoc.showDocument();

//以原始文档为原型,拷贝一份副本
WordDocument doc2 = (WordDocument)originDoc.clone();
doc2.showDocument();
//修改文档副本,不会影响原始文档
doc2.setmText("这是修改过的Doc2文本");
doc2.addmImages("哈哈。jpg"); //这一段时新加的
doc2.showDocument();
originDoc.showDocument();

}
}

结果

------WordDocument-----

--------word content start --------

Text :这是一篇文档

images List : 

image name :图片1

image name :图片2

image name :图片3

--------word content End --------

--------word content start --------

Text :这是一篇文档

images List : 

image name :图片1

image name :图片2

image name :图片3

--------word content End --------

--------word content start --------

Text :这是修改过的Doc2文本

images List : 

image name :图片1

image name :图片2

image name :图片3

image name :哈哈。jpg

--------word content End --------

--------word content start --------

Text :这是一篇文档

images List : 

image name :图片1

image name :图片2

image name :图片3

image name :哈哈。jpg (看这里,这个结果就改变了)

--------word content End --------

 

这是因为上文中WordDocument的clone方法中只是简单地进行浅拷贝,引用了新对象doc2的mImages只是单纯地指向了this.mImages引用,并没有重新构造一个mImages对象,这样就导致doc2中mImages与原始文档中的是同一个对象,因此,修改了其中一个文档中的图片,另一个文档也会受影响。

 

 

解决方法,就是采用深拷贝!!!

//clone这个方法是Object中的,如果没有Cloneable,却调用这个方法会报异常!!!
@Override
protected Object clone() throws CloneNotSupportedException {
try{
WordDocument doc = (WordDocument)super.clone();
doc.mText = this.mText;
// doc.mImages = this.mImages;
//对mImages对象也调用Clone()函数,进行深拷贝
doc.mImages = (ArrayList<String>)this.mImages.clone(); //这个地方是修改了
return doc;
}catch(Exception e){
return null;
}
}
结果

------WordDocument-----

--------word content start --------

Text :这是一篇文档

images List : 

image name :图片1

image name :图片2

image name :图片3

--------word content End --------

--------word content start --------

Text :这是一篇文档

images List : 

image name :图片1

image name :图片2

image name :图片3

--------word content End --------

--------word content start --------

Text :这是修改过的Doc2文本

images List : 

image name :图片1

image name :图片2

image name :图片3

image name :哈哈。jpg

--------word content End --------

--------word content start --------

Text :这是一篇文档

images List : 

image name :图片1

image name :图片2

image name :图片3   (现在就没有被改变)

--------word content End --------

分析

可以看出,Doc(栈),Doc2(栈),mText改变内容互不影响,但是mImages引用的都是同一个对象,所以第一个例子doc.mImages = this.mImages;所以引用的都是同一个对象,改变了就都改变了。

 

四.Android源码中使用到的原型模式

下面是一个发送短信的Intent对象

Uri uri = Uri.parse("smsto:08880000123");
Intent shareIntent = new Intent(Intent.ACTION_SENDTO,uri);
shareIntent.putExtra("sms_body","The SMS text");
//克隆副本
Intent intent = (Intent)shareIntent.clone();
startActivity(intent);
来看看Intent的clone方法是如何实现的:

@Override
public Object clone() {
return new Intent(this);
}

private Intent(Intent o, boolean all) {
this.mAction = o.mAction;
this.mData = o.mData;
this.mType = o.mType;
this.mPackage = o.mPackage;
this.mComponent = o.mComponent;
if (o.mCategories != null) {
this.mCategories = new ArraySet<String>(o.mCategories);
}
}
可以发现clone方法实际上在内部并没有调用super.clone()方法来实现对象拷贝,而是调用了new Intent(this);

(2)Intent的查找与匹配

<1>PackageManageService会在启动后,扫描已安装的apk目录,例如系统App的安装目录为/system/app,第三方的目录/data/app,然后解析apk包下的AndroidManifest.xml文件得到app的相关信息,而每个AndroidManifest.xml又包含了Activity,Service等组件的注册信息,当PMS扫描并且解析这些信息之后就构建整个apk的信息树,大致流程如下图

 

最后会把解析到的Activity,Service,添加到mActivities,mService中,这些类型定义是PackageManagerService的字段。

所以整个已安装apk的信息树就建立了,每个apk的应用名,包名,图标,activity,,service等信息都存储在系统中,当用户使用Intent跳转到Activity或者启动某个Service系统则会到这个信息表中进行查找,符合要求的组件则会被启动起来。

总结:

在系统启动时PackageManagerService会启动,此时PMS将解析所有已安装的应用的信息,构建一个信息表,当用户通过Intent来跳转所有已安装的应用的信息,构建一个信息表,当用户通过Intent来跳转到某个组件时,会根据Intent中包含的信息到PMS中查找对应的组件列表,最后跳转到目标组件中

 

五.原始模式实践

//login.java

/**
* Created by Administrator on 2016/3/13 0013.
*/
public interface Login {
void login();
}
//loginImpl.java

/**
* Created by Administrator on 2016/3/13 0013.
*/
public class LoginImpl implements Login {
private User loginedUser;
@Override
public void login() {
//登陆到服务器,获取用户信息
User loginedUser = new User();
//将服务器返回完整信息设置给LoginedUser对象
loginedUser.age = 22;
loginedUser.name = "Mr.Simple";
loginedUser.address = new MyAddress("北京市","海淀区","花园东路");

//登陆完之后将用户信息设置到Session 中LoginSession.getLoginSession()里
LoginSession.getLoginSession().setLoginedUser(loginedUser);
}
}
//LoginSession.java

/**
* Created by Administrator on 2016/3/13 0013.
*/
public class LoginSession {
static LoginSession sLoginSession = null;
//已登录用户
private User LoginedUser;
private LoginSession(){};
public static LoginSession getLoginSession(){
if(sLoginSession == null){
sLoginSession = new LoginSession();
}
return sLoginSession;
}
//设置已登录的用户信息,不对外开放
void setLoginedUser(User user){
LoginedUser = user;
}

public User getLoginedUser(){
// return LoginedUser;
//这样只拿到原始对象的拷贝而已,不能改变原来的值
return LoginedUser.clone();

}
}
//MyAddress.java

/**
* Created by Administrator on 2016/3/13 0013.
*/
public class MyAddress {
//城市
public String city;
//区
public String district;
//街道
public String street;
public MyAddress(String aCity,String aDist,String aStreet){
city = aCity;
district = aDist;
street = aStreet;
}

@Override
public String toString() {
return "Address [ city = "+city+", district="+district+", street="+street+"]";
}
}

//User.java

/**
* Created by Administrator on 2016/3/13 0013.
*/
public class User implements Cloneable {

public int age ;
public String name ;
public String phoneNum;
public MyAddress address;

@Override
public String toString() {
return "User [ age = "+age+", name="+name+", address="+address+"]";
}

@Override
protected User clone() {
User user = null;
try {
user = (User)super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return user;
}
}
//MainActivity.java

public class MainActivity extends Activity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//登陆
LoginImpl loginImpl = new LoginImpl();
loginImpl.login();
//获取登陆的user
User newUser = LoginSession.getLoginSession().getLoginedUser();
//打印现在用户的信息
System.out.println("xcqw原始用户"+newUser.toString());
//改变get获取用户的信息
newUser.address = new MyAddress("江西省","九江市","大树下");
System.out.println("xcqw直接改变数据"+LoginSession.getLoginSession().getLoginedUser().toString());
//通过set改变用户的信息
newUser.address = new MyAddress("江西省","九江市","大树下");
LoginSession.getLoginSession().setLoginedUser(newUser);
System.out.println("xcqw改变数据后,set一下" + LoginSession.getLoginSession().getLoginedUser().toString());

}

}

结果

xcqw原始用户User [ age = 22, name=Mr.Simple, address=Address [ city = 北京市, district=海淀区, street=花园东路]]

xcqw直接改变数据User [ age = 22, name=Mr.Simple, address=Address [ city = 北京市, district=海淀区, street=花园东路]]

xcqw改变数据后,set一下User [ age = 22, name=Mr.Simple, address=Address [ city = 江西省, district=九江市, street=大树下]]

 

可以看出效果达到了!!!

 

 

优点:

原型模式是在内存中二进制流的拷贝,要比直接new一个对象性能好很多,特别是要在一个循环体内产生大量的对象时,原型模式可以更好地体现其优点。

缺点:

直接在内存中拷贝,构造函数是不会执行的,这个既是优点又是缺点,在实际开发当中注意这个潜在的问题
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: