23种设计模式之回味代理模式(JAVA)
来源
代理模式的设计初衷是一个类自己不能办或者不想办的事委托给其他类去办,这样就为两个类之间的传值或者异步调用提供了巨大地方便。在移动开发中,无论是ios还是android,代理模式可以说是运用最为广泛而且尤为重要的一种设计模式。
特点
存在一个主体类,一个代理类,一个代理接口。将主体类不能办或者不想办的方法封装到代理接口中,让代理类实现这个接口,同时主体类保持对代理类对象的一个引用,通过这个引用在合适的地方调用代理方法。最终回收掉这个引用。
使用场合
代理模式运用比较广泛,比较典型的两个方面是
- 自定义控件添加交互功能时(比如点击事件),需要通过使用接口定义交互方法,在调用的地方再重写交互方法。
- 应用最多的方面是数据更新或者加载完毕时异步更新UI,比如下载一张图片然后再显示到ImageView,又比如说ListView的数据结构在另外一个类发生改变时需要更新UI等等。
简单实例
我Fairy会吃饭,会睡觉,也会coding,唯独不会打酱油。我想去打酱油怎么办?还好我兄弟XiaoMing会打酱油,就把打酱油的事委托给他帮我做吧。
首先看看打酱油的接口吧:
public interface SauceDelegate {
void BySauce();
}
再看看帮我打酱油的XiaoMing,他实现了打酱油这个接口:
public class XiaoMing implements SauceDelegate {
@Override
public void BySauce() {
// TODO Auto-generated method stub
System.out.println("酱油来了!");
}
}
再看看我Fairy,我想要打酱油,我要创建一个打酱油接口的对象作为代理,通过代理来调用打酱油:
public class Fairy {
private SauceDelegate delegate = null;
public void eating() {
}
public void sleeping() {
}
public void coding() {
}
public void setSauceDelegate(SauceDelegate delegate) {
this.delegate = delegate;
}
public Fairy() {
}
public void BySauce() {
if(delegate != null) {
delegate.BySauce();
}
}
}
最后看看,小明给我当代理,帮我打酱油吧。实则代理为XiaoMing对象的一个引用,最后当然要回收掉。
public class Main {
public static void main(String args[]) {
XiaoMing xiaoMing = new XiaoMing();
Fairy fairy = new Fairy();
fairy.setSauceDelegate(xiaoMing);
fairy.BySauce();
fairy.setSauceDelegate(null);
}
}
具体应用
一、在自定义控件中添加交互使用代理模式
案例太多了,就以一个简单的自定义的NavigationBar为例,先看看效果:
这是个比较简单的组合控件,包含三个部分,左边按钮,中间标题和右边按钮。具体布局如下,非正式项目(写得很随意),不做介绍:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="40dp"
android:paddingLeft="10dp"
android:paddingRight="10dp"
>
<TextView
android:id="@+id/edit"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="编辑"
android:background="@null"
android:textColor="#4ac4f4"
android:layout_centerVertical="true"
android:textSize="18sp"
android:clickable="true"
/>
<TextView
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="通讯录"
android:layout_centerVertical="true"
android:layout_centerHorizontal="true"
android:textSize="16sp"
/>
<TextView
android:id="@+id/add"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="+"
android:textColor="#4ac4f4"
android:textSize="18sp"
android:clickable="true"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
/>
<ImageView
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#60aaaaaa"
/>
</RelativeLayout>
<ImageView
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#60aaaaaa"
/>
</LinearLayout>
然后是代码实现这个组合控件,一般这种组合控件都继承一个布局,重写构造方法即可。这里要继承RelativeLayout,不要看xml中根布局是LinearLayout,我们要加交互和设置属性的是里面那个相对布局。
来看代码:
public class HeadView extends RelativeLayout implements View.OnClickListener{
private TextView left;
private TextView right;
private TextView title;
public HeadView(Context context) {
super(context);
// TODO Auto-generated constructor stub
}
public HeadView(Context context, AttributeSet attrs) {
super(context, attrs);
// TODO Auto-generated constructor stub
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflater.inflate(R.layout.head_model, this);
left = (TextView) findViewById(R.id.edit);
right = (TextView) findViewById(R.id.add);
title = (TextView) findViewById(R.id.title);
left.setOnClickListener(this);
right.setOnClickListener(this);
}
public HeadView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
// TODO Auto-generated constructor stub
}
public interface OnOperateListener {
void leftClick();
void rightClick();
}
private OnOperateListener mListener;
public void setOnOperateListener(OnOperateListener listener) {
mListener = listener;
}
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
switch(v.getId()) {
case R.id.edit:
mListener.leftClick();
break;
case R.id.add:
mListener.rightClick();
break;
}
}
public HeadView setTitle(CharSequence name) {
title.setText(name);
return this;
}
public HeadView setLeftText(CharSequence name) {
left.setText(name);
return this;
}
public HeadView setRightText(CharSequence name) {
right.setText(name);
return this;
}
}
因为用到了自定义布局,因此构造方法选择带属性集合参数的,一般就用第二个构造方法。里面加载布局,取得子控件实例不用多说。
重点看看对左右两个按钮添加单击事件,为了便于复用,不能把交互功能写在此类,而应该委托给使用这个组合控件的类来重写。于是委托模式就来了。
定义交互接口:
public interface OnOperateListener {
void leftClick();
void rightClick();
}
创建代理对象,需要代理方来初始化
private OnOperateListener mListener;
public void setOnOperateListener(OnOperateListener listener) {
mListener = listener;
}
通过代理调用交互处理:
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
switch(v.getId()) {
case R.id.edit:
mListener.leftClick();
break;
case R.id.add:
mListener.rightClick();
break;
}
}
在Activity的xml布局里再包含这个控件:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
>
<com.example.view.HeadView
android:id="@+id/head_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
<ListView
android:id="@+id/worker_list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
</LinearLayout>
最后看看代理方完成委托交互:
headView = (HeadView) findViewById(R.id.head_view);
headView.setTitle("增加人员").setLeftText("取消").setRightText("完成");
headView.setOnOperateListener(new OnOperateListener() {
@Override
public void leftClick() {
// TODO Auto-generated method stub
}
@Override
public void rightClick() {
// TODO Auto-generated method stub
}
});
这里没有以通过实现接口的方式而选择使用了匿名内部类,其实都可以,如果接口方法比较多的话一般用实现接口的方式。
可以这么说,自定义控件只要存在交互功能就绝对离不开代理模式。
二、数据变化更新UI使用代理模式
最后再来看看使用最广泛的一种情况,当我们的数据结构变化时,来更新UI。开发中一般使用两种办法来解决这个问题:使用观察者模式中的广播通知和使用代理模式。比较两者的优缺点:
- 广播通知:耦合度低、比较直接简单,但容易造成代码混乱,并且一个广播可能被多个观察者接收,造成不必要的浪费,同时还要考虑多线程的影响。
- 代理模式:被代理方与代理方一对一,可读性好,耦合度比广播高,且代码较多
综上,一般有相关性较高的两个类之间用代理模式,八竿子打不着的使用广播通知。多对一的情况使用广播通知,一对一的情况使用代理模式。
看看下面这个效果,联系人列表使用ListView,其item前面有一个CheckBox,选中某些联系人可以进行删除处理。其中一个要求就是当CheckBox的选中状态变化时,待删除的名单列表HashSet也要变化,同时删除按钮的text也要更新。
直接看来,没什么不同很好解决,唯一有点不好的是CheckBox的选中状态变化方法是在ListView的适配器中执行的。而UI更新包括删除操作是在Activity中执行的。这里就需要一个参数传递,将HashSet从适配器传到Activity。
有人想直接在适配器里面写一个get方法不就得了,CheckBox的onCheckedChange方法中更新HashSet不就行了。这样的话能取得到值,但却不是即使有效。CheckBox一变化,删除按钮UI就得更新。如果换用代理模式就非常爽了,适配器作为主体类,Activity作为代理类,适配器将更新UI的操作委托给Activity执行。
直接看代码,定义接口:
public interface CheckedChangeDelegate {
void onCheckedChangeUpdate(Set<String> set);
}
Acitivity实现这个接口,重写接口方法,根据适配器传入的HashSet更新UI:
@Override
public void onCheckedChangeUpdate(Set<String> set) {
// TODO Auto-generated method stub
int size = set.size();
deleteSet = set;
delete.setText("删除("+size+")");
if(size < 1) {
delete.setEnabled(false);
} else {
delete.setEnabled(true);
}
}
再在适配器这方创建一个代理,在构造方法中初始化代理(单独提出来也可以)并在CheckBox的onCheckedChange方法中调用更新方法:
private CheckedChangeDelegate delegate = null;
public WorkerListAdapter(Context context, List<Worker> workers, CheckedChangeDelegate delegate) {
this.context = context;
this.workers = workers;
this.delegate = delegate;
IdOfcheckedWorkers = new HashSet<String>();
}
holder.checkBox.setOnCheckedChangeListener(new OnCheckedChangeListener(){
@Override
public void onCheckedChanged(CompoundButton arg0, boolean isChecked) {
// TODO Auto-generated method stub
//Log.e("test", String.valueOf(isChecked));
if(isChecked) {
IdOfcheckedWorkers.add(worker.getId());
} else {
IdOfcheckedWorkers.remove(worker.getId());
}
delegate.onCheckedChangeUpdate(IdOfcheckedWorkers);
}
});
如此就达到更新的效果了,当然这里用广播通知也可以!
后记
鉴于以后开发ios的机会少,而且java更适用于设计模式的代码展现,故后面的文章都回归Java。
山的那边是海,海的那边还是山!