《Android源码设计模式解析与实战》读书笔记(四)
《Android源码设计模式解析与实战》PDF资料下载
一、原型模式简介
原型模式是一个创建型的模式。原型二字表明了该模式应该有一个样板实例,用户从这个样板对象中复制出一个内部属性一致的对象,这就是“克隆”。被复制的实例就是我们所称的“原型”。
原型模式多用于创建复杂的或者构造耗时的实例,因为这种情况下,复制一个已经存在的实例可使程序运行更高效。
1.1、定义
用原型实例指定创建对象的种类,并通过拷贝这些原型创建新的对象。
1.2、使用场景
- 类初始化需要消化非常多的资源,这个资源包括数据、硬件资源等,通过原型拷贝避免这些消耗。
- 通过new产生一个对象需要非常繁琐的数据准备或访问权限,这时可以使用原型模式。
- 一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时,可以考虑使用原型模式拷贝多个对象供调用者使用,即保护性拷贝。
注意:
通过实现Cloneable接口的原型模式在调用clone函数构造实例时并不一定比通过new操作速度快,只有当通过new构造对象较为耗时或者说成本较高时,通过clone方法才能获得效率上的提升。
二、原型模式的简单实现
/**
* 文档类型,扮演的是ConcretePrototype角色,而cloneable是代表prototype角色
*/
public class WordDocument implements Cloneable {
//文本
private String mText;
//图片名列表
private ArrayList<String> mImages = new ArrayList<>();
public WordDocument() {
System.out.println("----------------WordDocument构造函数-----------------");
}
@Override
protected WordDocument clone() {
try {
WordDocument doc = (WordDocument) super.clone();
doc.mText = this.mText;
doc.mImages = this.mImages;
return doc;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public String getText() {
return mText;
}
public void setText(String mText) {
this.mText = mText;
}
public ArrayList<String> getImages() {
return mImages;
}
public void addImages(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---------------");
}
}
Cloneable是一个标识接口,它表明这个类的对象是可拷贝的。如果没有实现Cloneable接口却调用了clone()函数将抛出异常。
调用代码如下:
WordDocument originDoc = new WordDocument();
//2.编辑文档,添加图片等
originDoc.setText("这是一篇文档");
originDoc.addImages("图片1");
originDoc.addImages("图片2");
originDoc.addImages("图片3");
originDoc.showDocument();
//以原始文档为原型,拷贝一份副本
WordDocument doc2 = originDoc.clone();
doc2.showDocument();
//修改文档副本,不影响原始文档
doc2.setText("这是修改过的Doc2文本");
doc2.showDocument();
originDoc.showDocument();
输出结果:
原型模式.pngdoc2是originDoc的一份拷贝,它们的内容是一样的,而doc2修改了文本内容以后并不会影响originDoc的文本内容,这就保证了originDoc的安全性。还需要注意,通过clone拷贝对象时并不会执行构造函数。
原型模式的核心问题就是对原始对象进行拷贝,在这个模式的使用过程中需要注意的一点就是:深、浅拷贝的问题。
三、原型模式实战
这是一个简化版的客户端,在用户登陆之后,通过LoginSession保存用户的登录信息,这些用户信息可能在APP的其他模块被用来做登录校验、用户个人信息显示等。但是,这些信息在客户端程序是不允许修改的,而需要在其他模块被调用,因此,需要开放已登录用户信息的访问接口。
/**
* 用户实体类
*/
public class User {
public int age;
public String name;
public String phoneNum;
public Address address;
@Override
public String toString() {
return "User{" +
"age=" + age +
", name='" + name + '\'' +
", phoneNum='" + phoneNum + '\'' +
", addrss=" + address +
'}';
}
}
/**
* 用户地址类,存储地址的详细信息
*/
public class Address {
//城市
public String city;
//区
public String district;
//街道
public String street;
public Address(String city, String district, String street) {
this.city = city;
this.district = district;
this.street = street;
}
@Override
public String toString() {
return "Address{" +
"city='" + city + '\'' +
", district='" + district + '\'' +
", street='" + street + '\'' +
'}';
}
}
//登陆接口
public interface Login {
void login();
}
//登录实现
public class LoginImpl implements Login {
@Override
public void login() {
// 登录到服务器,获取到用户信息
User loginedUser = new User();
//将服务器返回的完整信息设置给loginedUser对象
loginedUser.age = 22;
loginedUser.name = "Mr.Simple";
loginedUser.address = new Address("北京市", "海淀区", "花园东路");
//登录完之后将用户信息设置到Session中LoginSession.getLoginSession()里
LoginSession.getLoginSession().setLoginedUser(loginedUser);
}
}
//登录Session
public class LoginSession {
static LoginSession sLoginSession = null;
//已登录用户
private User loginedUser;
public LoginSession() {
}
public static LoginSession getLoginSession() {
if (sLoginSession == null) {
sLoginSession = new LoginSession();
}
return sLoginSession;
}
//设置已登录的用户信息,不对外开放
void setLoginedUser(User user) {
loginedUser = user;
}
public User getLoginedUser() {
return loginedUser;
}
}
LoginSession中的setLoginedUser函数是包级私有的,因此外部模块无法调用,这在一定程度上实现了外部客户端程序不能修改已登录的用户信息。
但是,也会存在类似如下代码:
//获取已登录的User对象
User newUser = LoginSession.getLoginSession().getLoginedUser();
newUser.address = new Address("北京市", "朝阳区", "大望路");
类似的代码也会更新用户的地址。因此,需要使用原型模式来进行保护性拷贝,也就是说在LoginSession的getLoginUser()函数中返回的是已登录用户的一个拷贝,当更新用户地址的网络请求完成时,在通过包级私有的LoginSession中的setLoginedUser更新用户信息。
于是在User类中覆写了clone方法:
/**
* 用户实体类
*/
public class User implements Cloneable {
public int age;
public String name;
public String phoneNum;
public Address address;
@Override
protected Object clone() throws CloneNotSupportedException {
User user = null;
try {
user = (User) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return user;
}
@Override
public String toString() {
return "User{" +
"age=" + age +
", name='" + name + '\'' +
", phoneNum='" + phoneNum + '\'' +
", addrss=" + address +
'}';
}
}
四、总结
原型模式本质上就是对象拷贝。使用原型模式可以解决构建复杂对象的资源消耗问题,能够在某些场景下提升创建对象的效率。还有一个重要的用途即使保护性拷贝。
优点:
- 原型模式是在内存中二进制流的拷贝,要比直接new一个对象性能好很多,特别是要在一个循环体内产生大量的对象时,原型模式可以更好地提现其优点。
缺点:
- 直接在内存中拷贝,构造函数是不会执行的,在实际开发当中应该注意这个潜在的问题。
网友评论