設(shè)計(jì)模式五(難逃法眼-觀察者模式)

前言

提起觀察者模式,LZ略微有點(diǎn)小小的激動(dòng),因?yàn)長(zhǎng)Z在工作中接觸的第一個(gè)項(xiàng)目就用到了觀察者模式,雖然LZ當(dāng)時(shí)處于一種懵逼狀態(tài),完全是復(fù)制粘貼代碼去完成業(yè)務(wù)需求,但那個(gè)項(xiàng)目的接觸,也讓LZ對(duì)觀察者模式有了一個(gè)初步的了解,此后一直對(duì)觀察者模式的理解處于朦朧狀態(tài),似懂非懂,直到前些天看到左盟主的博客才恍然大悟,有種相見(jiàn)恨晚的感覺(jué)。

先來(lái)簡(jiǎn)單描述一下LZ當(dāng)時(shí)項(xiàng)目中是什么樣的業(yè)務(wù)用到觀察者模式的。業(yè)務(wù)大致是這樣的,項(xiàng)目當(dāng)時(shí)牽扯到一些txt文件的上傳,文件的組要內(nèi)容是通信基站的一些狀態(tài)信息和描述,文件大多是批量上傳,上傳后要自動(dòng)解析并持久化到數(shù)據(jù)庫(kù),前臺(tái)沒(méi)有顯示的解析按鈕和請(qǐng)求,就意味著所有文件的上傳只要請(qǐng)求觸發(fā)就要通知解析功能對(duì)其進(jìn)行自動(dòng)解析。很明顯這樣的業(yè)務(wù)使用觀察者模式更合適不過(guò)了,LZ完全后知后覺(jué),先來(lái)看看觀察者模式吧!

定義

觀察者模式(有時(shí)又被稱(chēng)為發(fā)布-訂閱模式、模型-視圖模式、源-收聽(tīng)者模式或從屬者模式)是軟件設(shè)計(jì)模式的一種。在此種模式中,一個(gè)目標(biāo)物件管理所有相依于它的觀察者物件,并且在它本身的狀態(tài)改變時(shí)主動(dòng)發(fā)出通知。這通常透過(guò)呼叫各觀察者所提供的方法來(lái)實(shí)現(xiàn)。此種模式通常被用來(lái)實(shí)作事件處理系統(tǒng)。

上面的定義當(dāng)中,主要有這樣幾個(gè)意思,首先是有一個(gè)目標(biāo)的物件,通俗點(diǎn)講就是一個(gè)類(lèi),它管理了所有依賴(lài)于它的觀察者物件,或者通俗點(diǎn)說(shuō)是觀察者類(lèi),并在它自己狀態(tài)發(fā)生變化時(shí),主動(dòng)發(fā)出通知。

簡(jiǎn)單點(diǎn)概括成通俗的話來(lái)說(shuō),就是一個(gè)類(lèi)管理著所有依賴(lài)于它的觀察者類(lèi),并且它狀態(tài)變化時(shí)會(huì)主動(dòng)給這些依賴(lài)它的類(lèi)發(fā)出通知。

針對(duì)以上描述,我們先來(lái)看看百度給的類(lèi)圖


2017-11-19_142825.png

我們可以看到,被觀察類(lèi)Observable中只持有一個(gè)觀察者Observer的列表,當(dāng)被觀察者自己狀態(tài)發(fā)生改變時(shí),調(diào)用notifyObservers方法通知自己持有的這些觀察者列表中的對(duì)象,具體的觀察者Observer都是什么,被觀察者是不關(guān)心也不需要知道的。

上面就將觀察者和被觀察者二者的耦合度降到很低了,而我們具體的觀察者是必須要知道自己觀察的是誰(shuí),所以它依賴(lài)于被觀察者。

我們可以看到觀察者模式中有三個(gè)角色,一個(gè)是觀察者的抽象接口,一個(gè)是觀察者的具體實(shí)現(xiàn),一個(gè)是被觀察者,根據(jù)上邊的類(lèi)圖我們來(lái)寫(xiě)一段測(cè)試案例

(1)一個(gè)抽象的觀察者接口(用于定義觀察者發(fā)現(xiàn)被觀察者改變后所作出的相應(yīng)動(dòng)作)

/**
 * 觀察者接口
 */
public interface IObserver {
    void update(Observable o);
}

(2)一個(gè)被觀察者

/**
 * 被觀察者類(lèi)
 */
public class Observable {

    //觀察者列表
    List<IObserver> observers = new ArrayList<>();
    
    //給被觀察者添加觀察者
    public void addObserver(IObserver o){
        observers.add(o);
    }
    
    public void change(){
        System.out.println("被觀察者已經(jīng)發(fā)送改變~~~~~");
        //通知觀察者們
        notifyObservers();
    }

    private void notifyObservers() {
        for(IObserver o:observers){
            o.update(this);
        }   
    }
}

(3)若干個(gè)觀察者的具體實(shí)現(xiàn)類(lèi)

/**
 * 觀察者A
 */
public class ObserverA implements IObserver {
    public void update(Observable o) {
        System.out.println("觀察者A觀察到" + o.getClass().getSimpleName() + "發(fā)生變化");
        System.out.println("觀察者A做出相應(yīng)");
    }
}


/**
 * 觀察者B
 */
public class ObserverB implements IObserver {
    public void update(Observable o) {
        System.out.println("觀察者B觀察到" + o.getClass().getSimpleName() + "發(fā)生變化");
        System.out.println("觀察者B做出相應(yīng)");
    }
}

(4)測(cè)試類(lèi)

public class TestDemo {

    @Test
    public void fun1(){
        //創(chuàng)建一個(gè)觀察者
        Observable observable = new Observable();
        
        //給觀察者添加被觀察者
        observable.addObserver(new ObserverA());
        observable.addObserver(new ObserverB());
        
        //觀察者執(zhí)行改變方法
        observable.change();
    }
}
2017-11-19_145944.png

以上就是一個(gè)符合觀察者模式思想的簡(jiǎn)單demo,要注意的是,這里的被觀察者需要持有一個(gè)觀察者的列表,列表中填裝了觀察他的觀察者,它還需要有一個(gè)添加觀察者的方法,來(lái)吧需要觀察他的觀察者添加到觀察者列表中,最后還需要有一個(gè)通知方法notifyObservers,當(dāng)自己改變時(shí)通知那些觀察他的觀察者。

我們可以想象生活中的一個(gè)這個(gè)例子,買(mǎi)房子,買(mǎi)房者就是觀察者,房子就是被觀察者,當(dāng)房子價(jià)格變動(dòng)時(shí)通知購(gòu)房者,我們來(lái)試著寫(xiě)一寫(xiě)這個(gè)例子:
(1)首先我們定義一個(gè)購(gòu)房者的抽象接口人(觀察者接口)

/**
 * 定義一個(gè)觀察者接口(人)
 */
public interface People {
    //人有一個(gè)買(mǎi)房的動(dòng)作
    void buy(Home home);
}

(2)定義一個(gè)房子類(lèi)(被觀察者類(lèi))

/**
 * 一個(gè)房子類(lèi)(被觀察者)
 */
public class Home {

    //一個(gè)裝載購(gòu)買(mǎi)者的列表(觀察者列表)
    List<People> list = new ArrayList<>();
    
    //一個(gè)添加購(gòu)房者的方法
    public void addPeople(People people){
        list.add(people);
    }
    
    //一個(gè)自身的價(jià)格改變行為
    public void change(){
        System.out.println("房子價(jià)格有變動(dòng)!");
        notifyObservers();//通知那些觀察者(買(mǎi)房的人)
    }
    
    //一個(gè)通知購(gòu)房者的方法
    public void notifyObservers(){
        for (People people : list) {
            people.buy(this);
        }
    }
}

(3)若干個(gè)想購(gòu)買(mǎi)房子的人(觀察者實(shí)例)

/**
 * 路人甲(觀察者1)
 */
public class PeopleA implements People {
    public void buy(Home home) {
        System.out.println("購(gòu)房者甲觀察到" + home.getClass().getSimpleName() + "發(fā)生變化");
        System.out.println("購(gòu)房者甲做出了相應(yīng)決定");
    }
}



/**
 * 路人乙(觀察者2)
 */
public class PeopleB implements People {
    public void buy(Home home) {
        System.out.println("購(gòu)房者乙觀察到" + home.getClass().getSimpleName() + "發(fā)生變化");
        System.out.println("購(gòu)房者乙做出了相應(yīng)決定");
    }
}

(4)測(cè)試類(lèi)

public class App {
    public static void main(String[] args) {
        //創(chuàng)建一個(gè)房子
        Home home = new Home();
        //給房子添加觀察者
        home.addPeople(new PeopleA());
        home.addPeople(new PeopleB());
        
        //房子價(jià)格有所變動(dòng)
        home.change();
    }
}
2017-11-19_154754.png

好了,相比看了以上貼切生活的案例大家都對(duì)觀察者模式都有一個(gè)更深的了解,針對(duì)上邊的例子,大家有沒(méi)有發(fā)現(xiàn)一個(gè)問(wèn)題,我們的被觀察者是房子,通知我們價(jià)格變動(dòng)的通知者也是房子,問(wèn)題就在這里,房子本身不會(huì)說(shuō)話,如何通知我們,所以我們聯(lián)想到了銷(xiāo)售部或者房屋中介,所以這里還缺一個(gè)角色,被觀察者的管理者。以此引入java中自帶的觀察者模式

JDK為了方便開(kāi)發(fā)人員開(kāi)發(fā),已經(jīng)寫(xiě)好了現(xiàn)成兒的觀察者接口和被觀察者接口,下面我們看看有關(guān)java中提供的觀察者接口和被觀察者接口的源碼

(1)觀察者接口

//觀察者接口,每一個(gè)觀察者都必須實(shí)現(xiàn)這個(gè)接口
public interface Observer {
    //這個(gè)方法是觀察者在觀察對(duì)象產(chǎn)生變化時(shí)所做的響應(yīng)動(dòng)作,從中傳入了觀察的對(duì)象和一個(gè)預(yù)留參數(shù)
    void update(Observable o, Object arg);

}

(2)被觀察者接口

//被觀察者類(lèi)
public class Observable {
    //這是一個(gè)改變標(biāo)識(shí),來(lái)標(biāo)記該被觀察者有沒(méi)有改變
    private boolean changed = false;
    //持有一個(gè)觀察者列表
    private Vector obs;
    
    public Observable() {
    obs = new Vector();
    }
    //添加觀察者,添加時(shí)會(huì)去重
    public synchronized void addObserver(Observer o) {
        if (o == null)
            throw new NullPointerException();
    if (!obs.contains(o)) {
        obs.addElement(o);
    }
    }
    //刪除觀察者
    public synchronized void deleteObserver(Observer o) {
        obs.removeElement(o);
    }
    //notifyObservers(Object arg)的重載方法
    public void notifyObservers() {
    notifyObservers(null);
    }
    //通知所有觀察者,被觀察者改變了,你可以執(zhí)行你的update方法了。
    public void notifyObservers(Object arg) {
        //一個(gè)臨時(shí)的數(shù)組,用于并發(fā)訪問(wèn)被觀察者時(shí),留住觀察者列表的當(dāng)前狀態(tài),這種處理方式其實(shí)也算是一種設(shè)計(jì)模式,即備忘錄模式。
        Object[] arrLocal;
    //注意這個(gè)同步塊,它表示在獲取觀察者列表時(shí),該對(duì)象是被鎖定的
    //也就是說(shuō),在我獲取到觀察者列表之前,不允許其他線程改變觀察者列表
    synchronized (this) {
        //如果沒(méi)變化直接返回
        if (!changed)
                return;
            //這里將當(dāng)前的觀察者列表放入臨時(shí)數(shù)組
            arrLocal = obs.toArray();
            //將改變標(biāo)識(shí)重新置回未改變
            clearChanged();
        }
        //注意這個(gè)for循環(huán)沒(méi)有在同步塊,此時(shí)已經(jīng)釋放了被觀察者的鎖,其他線程可以改變觀察者列表
        //但是這并不影響我們當(dāng)前進(jìn)行的操作,因?yàn)槲覀円呀?jīng)將觀察者列表復(fù)制到臨時(shí)數(shù)組
        //在通知時(shí)我們只通知數(shù)組中的觀察者,當(dāng)前刪除和添加觀察者,都不會(huì)影響我們通知的對(duì)象
        for (int i = arrLocal.length-1; i>=0; i--)
            ((Observer)arrLocal[i]).update(this, arg);
    }

    //刪除所有觀察者
    public synchronized void deleteObservers() {
    obs.removeAllElements();
    }

    //標(biāo)識(shí)被觀察者被改變過(guò)了
    protected synchronized void setChanged() {
    changed = true;
    }
    //標(biāo)識(shí)被觀察者沒(méi)改變
    protected synchronized void clearChanged() {
    changed = false;
    }
    //返回被觀察者是否改變
    public synchronized boolean hasChanged() {
    return changed;
    }
    //返回觀察者數(shù)量
    public synchronized int countObservers() {
    return obs.size();
    }
}

下面我們用jdk提供的觀察者模式來(lái)修改一下上邊的購(gòu)房案例

(1)一個(gè)觀察者people(java要求觀察者實(shí)現(xiàn)Observer接口)

/**
 * 一個(gè)想購(gòu)買(mǎi)房子的觀察者
 */
public class People implements Observer {
    
    private String name;
    
    public People(String name) {
        super();
        this.name = name;
    }

    public String getName() {
        return name;
    }
    
    //關(guān)注
    public void subscribe(String homeName){
        HomeAgency.getInstance().getHome(homeName).addObserver(this);
    }
    
    //取消關(guān)注
    public void unsubscribe(String homeName){
        HomeAgency.getInstance().getHome(homeName).deleteObserver(this);
    }

    //當(dāng)樓盤(pán)房子價(jià)格有所變動(dòng),就去通知購(gòu)房者
    public void update(Observable o, Object arg) {
        if(o instanceof Home){
            Home home = (Home) o;
            System.out.println(name + "知道了" + home.getName() + "價(jià)格變?yōu)椋?" + home.getLastPrice() + ", 決定去買(mǎi)房子!");
        }   
    }
}

(2)一個(gè)被觀察者Home(java要求被觀察者需要繼承Observable類(lèi))

/**
 * 一個(gè)被觀察者(房子)
 */
public class Home extends Observable {

    //樓盤(pán)房子的名字
    private String name;

    //最后價(jià)格
    private Integer lastPrice;

    public Home(String name) {
        super();
        this.name = name;
        HomeAgency.getInstance().add(this);
    }
    
    public void changePrice(Integer price){
        System.out.println(name + "的價(jià)格有所改變,  改變后價(jià)格為: " + price + "平方米/元");
        lastPrice = price;
        //標(biāo)志改變和提醒
        setChanged();
        notifyObservers();
    }   
    public String getName() {
        return name;
    }   
    public Integer getLastPrice() {
        return lastPrice;
    }
}

(3)一個(gè)管理被觀察者的管理器(房屋中介)

/**
 * 被觀察者的管理器(房屋中介)
 */
public class HomeAgency {

    private Map<String,Home> homeMap = new HashMap<>();
    
    public void add(Home home){
        homeMap.put(home.getName(), home);
    }
    
    public Home getHome(String name){
        return homeMap.get(name);
    }
    
    //管理器單例
    private HomeAgency(){}
    
    public static HomeAgency getInstance(){
        return HomeAgencyInstance.instance;
    }
    
    private static class HomeAgencyInstance{
        private static HomeAgency instance = new HomeAgency();
    }
}

(4)測(cè)試類(lèi)

public class App {

    public static void main(String[] args) {
        //定義兩個(gè)樓盤(pán)    五個(gè)購(gòu)房者
        Home home1 = new Home("萬(wàn)科-樓盤(pán)");//毛坯
        Home home2 = new Home("恒大-樓盤(pán)");//精裝修
        People people1 = new People("東邪");
        People people2 = new People("西毒");
        People people3 = new People("南帝");
        People people4 = new People("北丐");
        People people5 = new People("中神通");
        
        //東南西北關(guān)注了恒大的樓盤(pán)(沒(méi)有錢(qián),只能買(mǎi)毛坯房)
        people1.subscribe("萬(wàn)科-樓盤(pán)");
        people2.subscribe("萬(wàn)科-樓盤(pán)");
        people3.subscribe("萬(wàn)科-樓盤(pán)");
        people4.subscribe("萬(wàn)科-樓盤(pán)");
        
        //中神通比較有錢(qián)  關(guān)注了精裝修的恒大樓盤(pán)
        people5.subscribe("恒大-樓盤(pán)");
        
        home1.changePrice(9000);
        home2.changePrice(13000);
        
        //后來(lái),中神通沒(méi)錢(qián)了,他就放棄了關(guān)注恒大樓盤(pán),轉(zhuǎn)去關(guān)注萬(wàn)科樓盤(pán)
        people5.unsubscribe("恒大-樓盤(pán)");
        people5.subscribe("萬(wàn)科-樓盤(pán)");
        
        //恒大樓盤(pán)再次漲價(jià)將不會(huì)通知中神通,反之萬(wàn)科樓盤(pán)價(jià)格變動(dòng)會(huì)通知中神通
        home1.changePrice(9100);
        home2.changePrice(150000);
    }
}

運(yùn)行結(jié)果


2017-11-19_165827.png

我們使用觀察者模式的用意是為了讓房子不在關(guān)系他價(jià)格變動(dòng)都去通知誰(shuí),更重要的是他不需要關(guān)系他通知的購(gòu)房者還是其他人,他只知道這個(gè)人實(shí)現(xiàn)了觀察者接口,即我們的觀察者依賴(lài)的只是一個(gè)抽象的觀察者接口,而不關(guān)心觀察者的具體實(shí)現(xiàn)。

另外讓購(gòu)房者來(lái)選擇自己關(guān)注的樓盤(pán),這相當(dāng)于被觀察者將維護(hù)通知對(duì)象的職能轉(zhuǎn)化給了觀察者,這樣做的好處是由于一個(gè)被觀察者可能有N多觀察者,所以讓被觀察者自己維護(hù)這個(gè)列表會(huì)很艱難,這就像一個(gè)老師被許多學(xué)生認(rèn)識(shí),那么是所有的學(xué)生都記住老師的名字簡(jiǎn)單,還是讓老師記住N多學(xué)生的名字簡(jiǎn)單?答案顯而易見(jiàn),讓學(xué)生們都記住一個(gè)老師的名字是最簡(jiǎn)單的。

以上就是設(shè)計(jì)模式中的觀察者模式

觀察者模式:發(fā)布(release)--訂閱(subscibe),變化(change)--更新(update)
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,505評(píng)論 6 533
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,556評(píng)論 3 418
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事?!?“怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 176,463評(píng)論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 63,009評(píng)論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,778評(píng)論 6 410
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 55,218評(píng)論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,281評(píng)論 3 441
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 42,436評(píng)論 0 288
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,969評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,795評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,993評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,537評(píng)論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,229評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 34,659評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 35,917評(píng)論 1 286
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,687評(píng)論 3 392
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,990評(píng)論 2 374

推薦閱讀更多精彩內(nèi)容

  • 1 場(chǎng)景問(wèn)題# 1.1 訂閱報(bào)紙的過(guò)程## 來(lái)考慮實(shí)際生活中訂閱報(bào)紙的過(guò)程,這里簡(jiǎn)單總結(jié)了一下,訂閱報(bào)紙的基本流程...
    七寸知架構(gòu)閱讀 4,641評(píng)論 5 57
  • 設(shè)計(jì)模式匯總 一、基礎(chǔ)知識(shí) 1. 設(shè)計(jì)模式概述 定義:設(shè)計(jì)模式(Design Pattern)是一套被反復(fù)使用、多...
    MinoyJet閱讀 3,960評(píng)論 1 15
  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,660評(píng)論 25 708
  • 本文的結(jié)構(gòu)如下: 什么是觀察者模式 為什么要用該模式 模式的結(jié)構(gòu) 代碼示例 推模型和拉模型 優(yōu)點(diǎn)和缺點(diǎn) 適用環(huán)境 ...
    w1992wishes閱讀 1,448評(píng)論 0 16
  • .題目1: dom對(duì)象的innerText和innerHTML有什么區(qū)別? innerText返回元素內(nèi)的的文本內(nèi)...
    大大的蘿卜閱讀 174評(píng)論 0 0