23種設計模式之回味代理模式(JAVA)

來源


代理模式的設計初衷是一個類自己不能辦或者不想辦的事委托給其他類去辦,這樣就為兩個類之間的傳值或者異步調用提供了巨大地方便。在移動開發(fā)中,無論是ios還是android,代理模式可以說是運用最為廣泛而且尤為重要的一種設計模式。

特點


存在一個主體類,一個代理類,一個代理接口。將主體類不能辦或者不想辦的方法封裝到代理接口中,讓代理類實現這個接口,同時主體類保持對代理類對象的一個引用,通過這個引用在合適的地方調用代理方法。最終回收掉這個引用。

使用場合


代理模式運用比較廣泛,比較典型的兩個方面是

  1. 自定義控件添加交互功能時(比如點擊事件),需要通過使用接口定義交互方法,在調用的地方再重寫交互方法。
  2. 應用最多的方面是數據更新或者加載完畢時異步更新UI,比如下載一張圖片然后再顯示到ImageView,又比如說ListView的數據結構在另外一個類發(fā)生改變時需要更新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,我想要打醬油,我要創(chuàng)建一個打醬油接口的對象作為代理,通過代理來調用打醬油:

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();
}

創(chuàng)建代理對象,需要代理方來初始化

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。開發(fā)中一般使用兩種辦法來解決這個問題:使用觀察者模式中的廣播通知和使用代理模式。比較兩者的優(yōu)缺點:

  • 廣播通知:耦合度低、比較直接簡單,但容易造成代碼混亂,并且一個廣播可能被多個觀察者接收,造成不必要的浪費,同時還要考慮多線程的影響。
  • 代理模式:被代理方與代理方一對一,可讀性好,耦合度比廣播高,且代碼較多

綜上,一般有相關性較高的兩個類之間用代理模式,八竿子打不著的使用廣播通知。多對一的情況使用廣播通知,一對一的情況使用代理模式。
看看下面這個效果,聯系人列表使用ListView,其item前面有一個CheckBox,選中某些聯系人可以進行刪除處理。其中一個要求就是當CheckBox的選中狀態(tài)變化時,待刪除的名單列表HashSet也要變化,同時刪除按鈕的text也要更新。



直接看來,沒什么不同很好解決,唯一有點不好的是CheckBox的選中狀態(tài)變化方法是在ListView的適配器中執(zhí)行的。而UI更新包括刪除操作是在Activity中執(zhí)行的。這里就需要一個參數傳遞,將HashSet從適配器傳到Activity。
有人想直接在適配器里面寫一個get方法不就得了,CheckBox的onCheckedChange方法中更新HashSet不就行了。這樣的話能取得到值,但卻不是即使有效。CheckBox一變化,刪除按鈕UI就得更新。如果換用代理模式就非常爽了,適配器作為主體類,Activity作為代理類,適配器將更新UI的操作委托給Activity執(zhí)行。
直接看代碼,定義接口:

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);
        }
    }

再在適配器這方創(chuàng)建一個代理,在構造方法中初始化代理(單獨提出來也可以)并在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);
            }
            
        });

如此就達到更新的效果了,當然這里用廣播通知也可以!

后記


鑒于以后開發(fā)ios的機會少,而且java更適用于設計模式的代碼展現,故后面的文章都回歸Java。
山的那邊是海,海的那邊還是山!

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容