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