C#使用IObservable和IObserver實(shí)現(xiàn)觀察者模式

觀察者模式是常用的設(shè)計(jì)模式,在.net環(huán)境下粉捻,其運(yùn)行時(shí)庫(kù)為開(kāi)發(fā)者提供了IObservable<T>IObserver<T>接口妈踊,用于實(shí)現(xiàn)觀察者模式軟件設(shè)計(jì)巷波。

IObservable<T>

    //
    // 摘要:
    //     定義基于推送的通知的提供程序瓷炮。
    //
    // 類(lèi)型參數(shù):
    //   T:
    //     提供通知信息的對(duì)象葱色。
    public interface IObservable<out T>
    {
        //
        // 摘要:
        //     通知提供程序:某觀察程序?qū)⒁邮胀ㄖ?        //
        // 參數(shù):
        //   observer:
        //     要接收通知的對(duì)象。
        //
        // 返回結(jié)果:
        //     使資源釋放的觀察程序的接口娘香。
        IDisposable Subscribe(IObserver<T> observer);
    }

注解Subscribe:

調(diào)用Subscribe通知提供程序某觀察程序?qū)⒁邮胀ㄖ醋?cè)苍狰、訂閱),不同于常規(guī)實(shí)現(xiàn)烘绽,它具有一個(gè)返回值淋昭,是一個(gè)IDisposable對(duì)象,當(dāng)觀察者不再接收通知時(shí)安接,可調(diào)用Dispose函數(shù)取消訂閱(反注冊(cè))翔忽,這種方法充分發(fā)揮C#語(yǔ)言的特性。

IObserver<in T>

//
// 摘要:
//     提供用于接收基于推送的通知的機(jī)制盏檐。
//
// 類(lèi)型參數(shù):
//   T:
//     提供通知信息的對(duì)象歇式。
public interface IObserver<in T>
{
    //
    // 摘要:
    //     通知觀察者,提供程序已完成發(fā)送基于推送的通知糯笙。
    void OnCompleted();
    //
    // 摘要:
    //     通知觀察者贬丛,提供程序遇到錯(cuò)誤情況。
    //
    // 參數(shù):
    //   error:
    //     一個(gè)提供有關(guān)錯(cuò)誤的附加信息的對(duì)象给涕。
    void OnError(Exception error);
    //
    // 摘要:
    //     向觀察者提供新數(shù)據(jù)。
    //
    // 參數(shù):
    //   value:
    //     當(dāng)前的通知信息额获。
    void OnNext(T value);
}

示例

下面例子演示觀察者設(shè)計(jì)模式够庙,實(shí)現(xiàn)定位系統(tǒng)實(shí)時(shí)通知當(dāng)前經(jīng)緯度坐標(biāo)。

包含經(jīng)緯度坐標(biāo)的Locaiton結(jié)構(gòu)體

public struct Location
{
    public Location(double latitude, double longitude)
    {
        Latitude = latitude;
        Longitude = longitude;
    }

    public double Latitude
    {
        get; private set;
    }

    public double Longitude
    {
        get;
        private set;
    }
}

LocationTracker 類(lèi)
實(shí)現(xiàn)了IObservable<T> 接口抄邀。

public class LocationTracker : IObservable<Location>
{
    public LocationTracker()
    {
        observers = new List<IObserver<Location>>();
    }

    private List<IObserver<Location>> observers;

    public IDisposable Subscribe(IObserver<Location> observer)
    {
        if (!observers.Contains(observer))
            observers.Add(observer);
        return new Unsubscriber(observers, observer);
    }
    // 用于取消訂閱通知的IDisposable對(duì)象的實(shí)現(xiàn)
    private class Unsubscriber : IDisposable
    {
        private List<IObserver<Location>> _observers;
        private IObserver<Location> _observer;

        public Unsubscriber(List<IObserver<Location>> observers, IObserver<Location> observer)
        {
            this._observers = observers;
            this._observer = observer;
        }

        public void Dispose()
        {
            if (_observer != null && _observers.Contains(_observer))
                _observers.Remove(_observer);
        }
    }
    // TrackLocation 方法傳遞了一個(gè)包含緯度和經(jīng)度數(shù)據(jù)的Location對(duì)象耘眨。 
    // 如果Location值不為null,則 TrackLocation 方法會(huì)調(diào)用每個(gè)觀察程序的 OnNext 方法境肾,
    // 否則調(diào)用OnError方法
    public void TrackLocation(Nullable<Location> loc)
    {
        foreach (var observer in observers)
        {
            if (!loc.HasValue)
                observer.OnError(new LocationUnknownException());
            else
                observer.OnNext(loc.Value);
        }
    }

    public void EndTransmission()
    {
        foreach (var observer in observers.ToArray())
            if (observers.Contains(observer))
                observer.OnCompleted();

        observers.Clear();
    }
}

LocationUnknownException類(lèi)

無(wú)法定位異常剔难,當(dāng)LocationTracker無(wú)法向它的觀察者提供定位時(shí),通過(guò)OnError方法通知觀察者當(dāng)前定位系統(tǒng)無(wú)法定位奥喻。

public class LocationUnknownException : Exception
{
   internal LocationUnknownException() 
   { }
}

LocationObserver類(lèi)

定位信息的觀察者偶宫,實(shí)現(xiàn)了IObserver<Location>接口

public class LocationReporter : IObserver<Location>
{
    private IDisposable unsubscriber;
    private string instName;

    public LocationReporter(string name)
    {
        this.instName = name;
    }

    public string Name
    { get { return this.instName; } }

    public virtual void Subscribe(IObservable<Location> provider)
    {
        if (provider != null)
            unsubscriber = provider.Subscribe(this);
    }

    public virtual void OnCompleted()
    {
        Console.WriteLine("The Location Tracker has completed transmitting data to {0}.", this.Name);
        this.Unsubscribe();
    }

    public virtual void OnError(Exception e)
    {
        Console.WriteLine("{0}: The location cannot be determined.", this.Name);
    }

    public virtual void OnNext(Location value)
    {
        Console.WriteLine("{2}: The current location is {0}, {1}", value.Latitude, value.Longitude, this.Name);
    }
    // 取消訂閱
    public virtual void Unsubscribe()
    {
        unsubscriber.Dispose();
    }
}

最后來(lái)看一下怎么使用這個(gè)定位系統(tǒng)

class Program2
{
    static void Main(string[] args)
    {
        // Define a provider and two observers.
        LocationTracker provider = new LocationTracker();
        LocationReporter reporter1 = new LocationReporter("FixedGPS");
        reporter1.Subscribe(provider);
        LocationReporter reporter2 = new LocationReporter("MobileGPS");
        reporter2.Subscribe(provider);

        provider.TrackLocation(new Location(47.6456, -122.1312));
        reporter1.Unsubscribe();
        provider.TrackLocation(new Location(47.6677, -122.1199));
        provider.TrackLocation(null);
        provider.EndTransmission();
    }
}

注解

很多時(shí)候被觀察者(IObservable)向觀察者(IObserver)提供的數(shù)據(jù)并不像Location這樣簡(jiǎn)單的結(jié)構(gòu)體。

而是一個(gè)包含復(fù)雜數(shù)據(jù)的類(lèi)环鲤,通炒壳鳎可能是被觀察者本身,這種情況是允許的,即IObserver<T> 實(shí)現(xiàn)和 T 可以表示同一類(lèi)型吵冒。

這時(shí)候的實(shí)現(xiàn)變成下面的型式:

public class LocationTracker2 : IObservable<LocationTracker>
{
    public IDisposable Subscribe(IObserver<LocationTracker> observer)
    {
        throw new NotImplementedException();
    }
}

public class LocationReporter2 : IObserver<LocationTracker2>
{
    public void OnCompleted()
    {
        throw new NotImplementedException();
    }

    public void OnError(Exception error)
    {
        throw new NotImplementedException();
    }

    public void OnNext(LocationTracker2 value)
    {
        throw new NotImplementedException();
    }
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末纯命,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子痹栖,更是在濱河造成了極大的恐慌亿汞,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,888評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件揪阿,死亡現(xiàn)場(chǎng)離奇詭異留夜,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)图甜,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,677評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén)碍粥,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人黑毅,你說(shuō)我怎么就攤上這事嚼摩。” “怎么了矿瘦?”我有些...
    開(kāi)封第一講書(shū)人閱讀 168,386評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵枕面,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我缚去,道長(zhǎng)潮秘,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,726評(píng)論 1 297
  • 正文 為了忘掉前任易结,我火速辦了婚禮枕荞,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘搞动。我一直安慰自己躏精,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,729評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布鹦肿。 她就那樣靜靜地躺著矗烛,像睡著了一般。 火紅的嫁衣襯著肌膚如雪箩溃。 梳的紋絲不亂的頭發(fā)上瞭吃,一...
    開(kāi)封第一講書(shū)人閱讀 52,337評(píng)論 1 310
  • 那天,我揣著相機(jī)與錄音涣旨,去河邊找鬼歪架。 笑死,一個(gè)胖子當(dāng)著我的面吹牛开泽,可吹牛的內(nèi)容都是我干的牡拇。 我是一名探鬼主播,決...
    沈念sama閱讀 40,902評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼惠呼!你這毒婦竟也來(lái)了导俘?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,807評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤剔蹋,失蹤者是張志新(化名)和其女友劉穎旅薄,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體泣崩,經(jīng)...
    沈念sama閱讀 46,349評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡少梁,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,439評(píng)論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了矫付。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片凯沪。...
    茶點(diǎn)故事閱讀 40,567評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖买优,靈堂內(nèi)的尸體忽然破棺而出妨马,到底是詐尸還是另有隱情,我是刑警寧澤杀赢,帶...
    沈念sama閱讀 36,242評(píng)論 5 350
  • 正文 年R本政府宣布烘跺,位于F島的核電站,受9級(jí)特大地震影響脂崔,放射性物質(zhì)發(fā)生泄漏滤淳。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,933評(píng)論 3 334
  • 文/蒙蒙 一砌左、第九天 我趴在偏房一處隱蔽的房頂上張望脖咐。 院中可真熱鬧,春花似錦绊困、人聲如沸文搂。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,420評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至笔喉,卻和暖如春取视,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背常挚。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,531評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工作谭, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人奄毡。 一個(gè)月前我還...
    沈念sama閱讀 48,995評(píng)論 3 377
  • 正文 我出身青樓折欠,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子锐秦,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,585評(píng)論 2 359

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