设计模式系列:
0. Android开发常用设计模式;
一、常见需求场景
三毛:“小白,如下图,像淘宝里的商品,选择对应的颜色或尺寸就把该商品加入购物车,你觉得怎么去做呢”
二、基本解决方法
小白:这个直接new一个商品对象,把选择的东西set进去就好了吖,就像人,假设只有名字不一样,其他都一样,不也是直接new一个对象就好了吗,像下面这样
for (int i = 0; i < 10; i++) {
Person person = new Person();
person.setName("我名字是:" + i);
}
三、基本解决方法存在的问题
三毛:”要是你上面那个对象属性很多也复杂,如果没加一个商品到购物车就new这样一个对象,岂不是很消耗内存和资源,造成卡顿什么的嘛”
小白:但是我不能只new一个对象吧,肯定是一件商品new一个对象啊,好比人,如果改成下面这样,值new一个的话,每次setName都会把我前面的名字给修改了
Person person = new Person();
person.setName("我名字是0");
for (int i = 1; i < 10; i++) {
person.setName("我名字是:" + i);
}
三毛:“你可以使用原型设计模式决解这类问题”
小白:三毛哥,啥是原型模式?
四、原型模式写法
原型模式定义:原型模式是一种创建型的设计模式,通过已有的对象克隆出新的对象,使我们的程序运行的更高效。
小白: 怎么个克隆法捏
三毛:“用一个对象实现Cloneable接口,复写clone方法,例如下面”
//解释一下:clone这个方法不是Cloneable接口中的clone是Object中的方法,Cloneable是一个标识接口,它表明这个类的对象是可以拷贝的。如果没有实现Cloneable接口却调用了clone()函数将抛出异常。(自己可以点击进去看下源码)
public class Person implements Cloneable {
private String name;
@Override
protected Object clone(){
Person person = null;
try {
person = (Person) super.clone();//克隆已有的对象
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
person.name = this.name;//把已有的属性值复制给克隆的对象里的属性
return person;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
//然后你就可以使用了
Person person = new Person();
person.setName("我名字是0");
for (int i = 1; i < 10; i++) {
Person p1= (Person) person.clone();
p1.setName("我名字是:"+i);
}
小白: Person p1= (Person) person.clone()这样和我直接new出来有啥区别哇,完全不懂
三毛:”你点击super.clone()进源码你会看到它的代码是下面这样的”
//super.clone()源码里面
protected Object clone() throws CloneNotSupportedException {
if (!(this instanceof Cloneable)) {//这就是为嘛对象要实现Cloneable这个接口,不然给你抛异常
throw new CloneNotSupportedException("Class " + getClass().getName() +
" doesn't implement Cloneable");
}
return internalClone();
}
/*
* Native helper method for cloning.
*/
//native方法,一般而言,对JVM来说,native方法的效率远比java中的普通方法高
//所以才推荐你使用原型模式,而不是new
private native Object internalClone();
小白:喔,那原型模式有什么要注意的地方嘛
三毛:“嗯,使用原型模式的时候要注意下面几点”
1)某些对象构造非常简单的情况下,就不要滥用原型模式了;
2)使用原型模式复制对象不会调用类的构造方法(new一个对象才会调用构造方法)。因为对象的复制是通过调用Object类的clone方法来完成的,它直接在内存中复制数据,也就是内存中二进制流的拷贝;
3)原型模式分为深拷贝和浅拷贝
浅拷贝:Object类的clone方法只会拷贝对象中的基本的数据类型(8种基本数据类型byte,char,short,int,long,float,double,boolean),
深拷贝:除了浅拷贝的8中基本数据类型,其他都属于深拷贝,例如数组、对象...
例如对象中有不属于8种基本数据类型时也用浅拷贝错误做法
//错误写法
//Person里有2个数据类型不属于浅拷贝的8种基本数据类型,却用了浅拷贝方式
public class Person implements Cloneable {
private String name;
private Action action;//action是个对象
private ArrayList<String> listStr = new ArrayList<>();//数组
@Override
public Object clone(){
Person person = null;
try {
person = (Person) super.clone();//克隆已有的对象
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
person.name = this.name;//把已有的属性值复制给克隆的对象里的属性
person.action = this.action;//对象(引用),当成了基本数据类型处理
person.listStr = this.listStr;//数组(引用),当成了基本数据类型处理
return person;
}
//set、get方法省略.....
}
正确写法
public class Person implements Cloneable {
private String name;
private Action action;//action是个对象
private ArrayList<String> listStr = new ArrayList<>();//数组
@Override
public Object clone(){
Person person = null;
try {
person = (Person) super.clone();//克隆已有的对象
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
person.name = this.name;//把已有的属性值复制给克隆的对象里的属性
person.action = (Action) this.action.clone();//要调用clone
person.listStr = (ArrayList<String>) this.listStr.clone();//要调用clone
return person;
}
}
//Action类
public class Action implements Cloneable{
//省略属性
@Override
public Object clone(){
Action action = null;
try {
action = (Action) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return action;
}
}
小白:毛毛哥,如果我使用上面的错误写法,结果会咋样吖
三毛:使用错误写法的话会造成下面结果
//克隆前的数据
Person person = new Person();
person.setName("小明");
Action action = new Action();
action.setActionType("吃饭");
ArrayList<String> listStr = new ArrayList<>();
listStr.add("好的");
person.setAction(action);
person.setListStr(listStr);
System.out.println("---------原person---------");
System.out.println(person.toString());
//clone(克隆)一份后
Person p = (Person) person.clone();//拷贝一个对象
p.getAction().setActionType("睡觉");
p.getListStr().add("不要");
System.out.println("---------clone的person---------");
System.out.println(p.toString());
System.out.println("---------原person---------");
//打印的结果:
System.out: -----------原person-----------
Person{name='小明', action=Action{actionType='吃饭'}, listStr=[好的]}
System.out: -----------clone的person-----------
System.out: Person{name='小明', action=Action{actionType='睡觉'}, listStr=[好的, 不要]}
System.out: -----------原person-----------
System.out: Person{name='小明', action=Action{actionType='睡觉'}, listStr=[好的, 不要]}
//从上面我们可以看到clone后,给clone的对象设置新数据,我们原来的Person也给改变了,这不是我们想要的,我们只想设置clone的对象的值,并不想动原来Person的值
小白:喔,我明白了,这个深拷贝就是是典型的嵌套,如果类里面有不是基本数据类型的就要像person.action = (Action) this.action.clone()对不是基本数据类型在一次clone
三毛:“嗯,像在Android中Intent,Bundle也用到了原型模式,打开源码可以看到如下”
//源码里意图的原型模式写法
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);
}
}
@Override
public Object clone() {
return new Intent(this);
}
//源码里Bundle原型模式写法
public Bundle(Bundle b) {
super(b);
mFlags = b.mFlags;
}
@Override
public Object clone() {
return new Bundle(this);
}
小白:奥!怪不得以前看不懂,原来是原型模式啊
三毛:“嗯,Android系统中很多地方都用到了原型模式,除此之外,像比较有名的OkHttp、realm数据库…都用到了原型模式呢”
五、原型模式和普通写法区别
1、每个clone出来的实例公共对象都是不同的,不会相互影响;
2、原型模式是在内存中二进制流的拷贝,要比直接 new 一个对象性能好很多(对象结构简单的就算了)