23种设计模式之回味代理模式(JAVA)

来源


代理模式的设计初衷是一个类自己不能办或者不想办的事委托给其他类去办,这样就为两个类之间的传值或者异步调用提供了巨大地方便。在移动开发中,无论是ios还是android,代理模式可以说是运用最为广泛而且尤为重要的一种设计模式。

特点


存在一个主体类,一个代理类,一个代理接口。将主体类不能办或者不想办的方法封装到代理接口中,让代理类实现这个接口,同时主体类保持对代理类对象的一个引用,通过这个引用在合适的地方调用代理方法。最终回收掉这个引用。

使用场合


代理模式运用比较广泛,比较典型的两个方面是

  1. 自定义控件添加交互功能时(比如点击事件),需要通过使用接口定义交互方法,在调用的地方再重写交互方法。
  2. 应用最多的方面是数据更新或者加载完毕时异步更新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。
山的那边是海,海的那边还是山!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,711评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,932评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,770评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,799评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,697评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,069评论 1 276
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,535评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,200评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,353评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,290评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,331评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,020评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,610评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,694评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,927评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,330评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,904评论 2 341

推荐阅读更多精彩内容