一.使用背景
(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一个对象性能好很多,特别是要在一个循环体内产生大量的对象时,原型模式可以更好地体现其优点。
缺点:
直接在内存中拷贝,构造函数是不会执行的,这个既是优点又是缺点,在实际开发当中注意这个潜在的问题