觀察者模式
觀察者模式是為了滿足監(jiān)聽(tīng)的需求。也就是說(shuō)當(dāng)某件事情發(fā)生的時(shí)候, 一個(gè)或多個(gè)觀察者需要獲知此事件的發(fā)生, 如果每個(gè)觀察者都采用輪詢的方式判斷事件是否發(fā)生,則會(huì)耗費(fèi)較多的資源。所以這個(gè)任務(wù)就應(yīng)該由被觀察者來(lái)完成, 即被觀察者持有多個(gè)觀察者對(duì)象, 當(dāng)自身某事件發(fā)生的時(shí)候, 去通知所有觀察者。這樣一種機(jī)制就是觀察者模式。
但是這其中會(huì)有一些安全問(wèn)題,比如說(shuō)被觀察者持有觀察者對(duì)象,這時(shí)觀察者就完全暴露給了被觀察者,這種情況應(yīng)該避免出現(xiàn)。所以就自然引出了接口的概念,以一個(gè)接口來(lái)統(tǒng)一觀察者的行為, 被觀察者只持有該接口, 任何一個(gè)實(shí)現(xiàn)了該接口的對(duì)象,都可以作為觀察者被其持有, 而對(duì)象的其他細(xì)節(jié)則不對(duì)被觀察者開(kāi)放。
Java中已經(jīng)封裝了觀察者模式,我們可以仿照它的寫(xiě)法來(lái)實(shí)現(xiàn)自己的觀察者模式
首先, 由我們之前提到的, 觀察者應(yīng)該提供統(tǒng)一接口用來(lái)讓被觀察者調(diào)用,即:
Observer
public interface Observer {
void update(Observable observable, Object object);
}
對(duì)于觀察者,則比較復(fù)雜, 我們可以找出幾個(gè)主要的步驟:
| 序號(hào) | 步驟 |
|---|---|
| 1 | 持有觀察者 |
| 2 | 判斷是否有改變 |
| 3 | 通知觀察者 |
這樣我們就可以寫(xiě)出被觀察者的抽象類:
public abstract class Observable {
private Vector<Observer>observers = new Vector<>();
private boolean isChanged;
public void addObserver(Observer observer){
observers.add(observer);
}
public void setChanged(){
isChanged = true;
}
public void notifyObservers(Object object){
if (isChanged) {
observers.forEach(observer -> observer.update(this, object));
isChanged = false;
}
}
}
這樣我們就定義了觀察者的接口,以及被觀察者的抽象類
其實(shí),在jdk的實(shí)現(xiàn)中,Observable 會(huì)有完善的線程安全保護(hù), 比如存放觀察者的List是以Vector來(lái)實(shí)現(xiàn)的,而Vector本身就是線程安全的, 再比如Observable 中的方法都加上了synchronize標(biāo)識(shí)符, 以線程安全的方式來(lái)通知觀察者。但是這些在這里都不是重點(diǎn),所以就不再詳述</font>
下面看一個(gè)實(shí)際應(yīng)用:
在我們的例子中,有一名學(xué)生和若干老師, 學(xué)生會(huì)提出一些問(wèn)題,這些問(wèn)題則會(huì)提交給老師(通知觀察者),老師則判斷自己是否會(huì)做,給學(xué)生反饋。>在這里我們有兩位老師,一位是數(shù)學(xué)老師, 一位是美術(shù)老師, 目前的設(shè)定是他們每人只回答一個(gè)問(wèn)題, 遇到其余的問(wèn)題則會(huì)跳過(guò) 。
看一下具體代碼:
Tea_Math
public class Tea_Math implements Observer{
private String name = "數(shù)學(xué)老師:";
@Override
public void update(Observable observable, Object object) {
String question = (String) object;
if(question.equals("矩陣相乘的意義是什么呢?")){
System.out.println(name +"從某種角度來(lái)說(shuō), 是坐標(biāo)的變換");
}else {
System.out.println(name +"我不太清楚, 你問(wèn)問(wèn)其他老師");
}
}
}
Tea_Art
public class Tea_Art implements Observer{
private String name = "美術(shù)老師:";
@Override
public void update(Observable observable, Object object) {
String question = (String) object;
if(question.equals("莫奈的睡蓮是他晚年的作品嗎?")){
System.out.println(name +"是他晚年一系列的作品");
}else {
System.out.println(name +"我不太清楚, 你問(wèn)問(wèn)其他老師");
}
}
}
Stu
public class Stu extends Observable{
public void ask(String question){
System.out.println("question:" + question);
setChanged();
notifyObservers(question);
}
}
實(shí)際調(diào)用:
public class Client {
public static void main(String[] args) {
Stu stu = new Stu();
Tea_Math tea_Math = new Tea_Math();
Tea_Art tea_Art = new Tea_Art();
stu.addObserver(tea_Math);
stu.addObserver(tea_Art);
stu.ask("矩陣相乘的意義是什么呢?");
stu.ask("莫奈的睡蓮是他晚年的作品嗎?");
}
}
我們可以看一下運(yùn)行結(jié)果:
question:矩陣相乘的意義是什么呢?
數(shù)學(xué)老師:從某種角度來(lái)說(shuō), 是坐標(biāo)的變換
美術(shù)老師:我不太清楚, 你問(wèn)問(wèn)其他老師
question:莫奈的睡蓮是他晚年的作品嗎?
數(shù)學(xué)老師:我不太清楚, 你問(wèn)問(wèn)其他老師
美術(shù)老師:是他晚年一系列的作品
以上就是觀察者模式的說(shuō)明以及代碼實(shí)現(xiàn),應(yīng)該是比較清晰的。
回調(diào):
回調(diào)是則是為了滿足調(diào)用者的需求而設(shè)計(jì)的。如調(diào)用者需要執(zhí)行某個(gè)動(dòng)作, 并且它要自己定義完成該動(dòng)作后該做什么工作。這個(gè)時(shí)候該動(dòng)作是調(diào)用者自己發(fā)出的, 但是這個(gè)動(dòng)作的完成需要交由被調(diào)用者來(lái)實(shí)現(xiàn), 這樣的話, 調(diào)用者該如何知道完成該動(dòng)作后接下來(lái)做什么呢?它怎么才能知道調(diào)用者定義的后續(xù)工作呢?這時(shí)候就需要用到回調(diào), 要實(shí)現(xiàn)一個(gè)回調(diào)的基本步驟有:
| 序號(hào) | 步驟 |
|---|---|
| 1 | 調(diào)用者定義某個(gè)動(dòng)作 |
| 2 | 然后指定執(zhí)行該動(dòng)作的被調(diào)用者(持有被調(diào)用者) |
| 3 | 再定義動(dòng)作完成后需要執(zhí)行的后續(xù)動(dòng)作 |
| 4 | 最后將這個(gè)后續(xù)動(dòng)作告知被調(diào)用者(或者可以說(shuō)將這個(gè)后續(xù)動(dòng)作的調(diào)用方法交給被調(diào)用者)。通常情況下, 被調(diào)用者是系統(tǒng)應(yīng)用, 也就是說(shuō), 我們將自己的后續(xù)動(dòng)作告知系統(tǒng)應(yīng)用, 讓其完成后執(zhí)行我們的操作。 |
可以用點(diǎn)擊事件的實(shí)現(xiàn)來(lái)深入一下:
如有一個(gè)TextView作為調(diào)用者, 一個(gè)OnClickListener作為被調(diào)用者TextView應(yīng)該持有OnClickListener, 所以它有一個(gè)setOnClickListener方法而且它要執(zhí)行onClick動(dòng)作,所以它有click方法。而對(duì)于被調(diào)用者OnClickListener來(lái)說(shuō), 它是click動(dòng)作的實(shí)際完成者,所以它有onClick方法。
這樣的話就可以寫(xiě)出二者的結(jié)構(gòu):
TextView
public class TextView {
private OnClickListener onClickListener;
public void setOnClickListener(OnClickListener onClickListener){
this.onClickListener = onClickListener;
}
public void click(){
onClickListener.onClick("textview", 1);
}
}
OnClickListener
public interface OnClickListener {
void onClick(String view, int position);
}
實(shí)際調(diào)用:
public class Client_Click {
public static void main(String[] args) {
TextView textView = new TextView();
textView.setOnClickListener(new OnClickListener() {
@Override
public void onClick(String view, int position) {
System.out.println(view);
System.out.println(position);
}
});
textView.click();
}
}
上面的代碼已經(jīng)比較清晰了, 應(yīng)該可以理解回調(diào)的意義了
對(duì)于回調(diào)和觀察者模式的聯(lián)系和區(qū)別, 以及他們的適用環(huán)境,可以結(jié)合實(shí)際情況來(lái)判斷。我打算過(guò)些日子有時(shí)間詳細(xì)的寫(xiě)一下。