基本概念
装饰模式是扩展功能用的一种设计模式,
一般要扩展功能,我们都会想到继承,可是继承只能继承一个基类,如果有多个条件需要分别进行扩展,那得写好几个派生类,条件越多派生类的数量也越多。
上面描述比较抽象,还是举个例子来说明。比如人分男人和女人,先建个Human基类,再建Man和Woman两个派生类。同时人又有不同国籍,比如说中国男人、日本女人等等,此时再创建ChinaMan、ChinaWoman、JapanMan、JapanWoman四个派生类,其中ChinaMan和JapanMan继承自Man类,ChinaWoman和JapanWoman继承自Woman类。同时,同一国籍的人又有相同的行为动作,比如说中国人写中文,日本人写日文,所以ChinaMan和ChinaWoman理应继承自一个名为中国人的类,JapanMan和JapanWoman理应继承自一个名为日本人的类;但现实情况是,ChinaMan继承自Man类,ChinaWoman继承自Woman类,已经无法再继承其他类了,因此只能在这两个类中各自实现中国人的动作,当然实现一个中国人的接口也是办法。
为解决上面这个窘境,我们可以引入装饰模式加以优化。装饰模式把成员分为四个角色:
1、抽象基类:定义该集合将要使用的基本属性和方法。
2、初步实现的派生类:由抽象基类简单派生而来,并实现普通的构造函数。
3、待装饰的基类:定义抽象基类的一个实例,并实现一个基于对象的构造函数。
4、装饰好的派生类:由待装饰的基类派生出来,可进行定制化处理。
简单用法
讲概念很容易把人搞得一头雾水,博主也是一样,接下来还是直接上个代码例子好了,通过装饰模式实现男人、女人、中国人、美国人的交叉继承。
磨刀不误砍柴工,我们规划一下四个成员角色的类名分布,如下所示:
1、抽象基类:Human
2、初步实现的派生类:Person
3、待装饰的基类:People
4、装饰好的派生类:Man/Woman/Chinese/American
首先创建一个抽象基类Human,代码示例如下:
public abstract class Human {
private String name;
private String nation;
private int sex;
protected void setName(String name) {
this.name = name;
}
protected String getName() {
return this.name;
}
protected void setNation(String nation) {
this.nation = nation;
}
protected String getNation() {
return this.nation;
}
protected void setSex(int sex) {
this.sex = sex;
}
protected int getSex() {
return this.sex;
}
protected String showInfo() {
String desc = String.format("%s是个%s人,%s是%s人",
getName(), (getSex()==0)?"男":"女", (getSex()==0)?"他":"她", getNation());
return desc;
}
}
然后创建初步实现的派生类Person,代码示例如下:
public class Person extends Human {
public Person(String name) {
setName(name);
}
}
接着创建待装饰的基类People,代码示例如下:
public abstract class People extends Human {
private Human mBase;
public People(Human base) {
mBase = base;
}
protected void setName(String name) {
mBase.setName(name);
}
protected String getName() {
return mBase.getName();
}
protected void setNation(String nation) {
mBase.setNation(nation);
}
protected String getNation() {
return mBase.getNation();
}
protected void setSex(int sex) {
mBase.setSex(sex);
}
protected int getSex() {
return mBase.getSex();
}
}
再来创建各个装饰好的派生类,如男人的类Man、女人的类Woman、中国人的类Chinese、美国人的类American,代码示例如下:
Man.java
public class Man extends People {
public Man(Human base) {
super(base);
setSex(0);
}
}
Woman.java
public class Woman extends People {
public Woman(Human base) {
super(base);
setSex(1);
}
}
Chinese.java
public class Chinese extends People {
public Chinese(Human base) {
super(base);
setNation("中国");
}
}
American.java
public class American extends People {
public American(Human base) {
super(base);
setNation("美国");
}
}
最后是装饰类的调用代码例子:
public class Wrapper {
public static void main(String[] arg) {
Human one = new Chinese(new Man(new Person("张三")));
Human two = new Chinese(new Woman(new Person("邓丽君")));
Human three = new American(new Man(new Person("乔布斯")));
Human four = new American(new Woman(new Person("露茜")));
System.out.println(one.showInfo());
System.out.println(two.showInfo());
System.out.println(three.showInfo());
System.out.println(four.showInfo());
}
}
下面是程序运行结果:
张三是个男人,他是中国人
邓丽君是个女人,她是中国人
乔布斯是个男人,他是美国人
露茜是个女人,她是美国人
Android中的使用场合
Android开发中不用装饰模式则已,一用装饰模式都是用在复杂之处,具体的说,主要有输入输出流、ContextWrapper两类例子。
输入输出流
java.io包下的InputStream和OutputStream类及其派生类,构成了装饰模式的一大家族,比如下面这行代码:
BufferedReader input = new BufferedReader(new InputStreamReader(System.in));
分析可知,这行代码用到的类与装饰模式角色的对应关系如下:
Reader类对应抽象基类,InputStreamReader类对应初步实现的派生类,BufferedReader类同时对应待装饰的基类与装饰好的派生类,因为这里业务不复杂,所以BufferedReader类同时身兼两职务。
另外一个io流的装饰模式角色如下:
1、抽象基类:InputStream
2、初步实现的派生类:FileInputStream
3、待装饰的基类:FilterInputStream
4、装饰好的派生类:BufferedInputStream/DataInputStream/LineNumberInputStream
下面是文件io流中运用装饰模式的代码例子,有关文件读写的介绍参见《 Android开发笔记(三十三)文本文件和图片文件的读写》。
private Bitmap openBitmap(String path) {
Bitmap bitmap = null;
try {
BufferedInputStream bis = new BufferedInputStream(
new FileInputStream(path));
bitmap = BitmapFactory.decodeStream(bis);
bis.close();
} catch (Exception e) {
e.printStackTrace();
}
return bitmap;
}
ContextWrapper
我们熟悉的Activity和Service就使用了装饰者模式,博主这种小白刚发现时连嘴巴都惊讶的合不拢了。我们先列出装饰模式四种角色与相关类的对应关系:
1、抽象基类:Context。Context是一个抽象类,里面包含了各种方法的声明,如setTheme、getPackageName、getSharedPreferences、getSystemService、startActivity、sendBroadcast、registerReceiver、unregisterReceiver、startService、stopService等等。
2、初步实现的派生类:ContextImpl。ContextImpl是真正实现Context的类,该类在android.jar里面找不到,要查看sdk的源码才能找到。
3、待装饰的基类:ContextWrapper。ContextWrapper是Context的包装器,里面定义了Context的一个实例mBase,以及基于Context对象的构造函数,内部所有的操作都是调用mBase的方法。
4、装饰好的派生类:Activity/Service/Application。这几个类都是从ContextWrapper派生而来。
Context使用装饰模式的好处,除了在Activity/Service/Application上体现外,还体现在如下两个方面:
1、BroadcastReciver。虽然它本身不是继承自Context,内部也没有Context实例,但是每当一个新广播到达时,框架都传递一个Context对象给onReceive()。这个Context对象是一个ReceiverRestrictedContext实例,它禁用了两个函数:registerReceiver()和bindService(),即在接收广播时不允许注册新广播,也不允许绑定服务。
2、ContentProvider。它本身也不是继承自Context,但是它可以通过getContext()函数返回一个Context对象。如果ContentProvider是在同一个进程中调用,getContext()将返回当前Application的实例。但是,如果ContentProvider在其它进程中调用,getContext()将返回一个代表运行包的新实例。
以上提到与Context相关的Android四大组件的介绍参见:
《 Android开发笔记(三十九)Activity的生命周期》
《 Android开发笔记(四十一)Service的生命周期》
《 Android开发笔记(四十二)Broadcast的生命周期》
《 Android开发笔记(五十四)数据共享接口ContentProvider》
点此查看Android开发笔记的完整目录