<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ??碼云GVP開源項目 12k star Uniapp+ElementUI 功能強大 支持多語言、二開方便! 廣告
                # 觀察者模式 觀察者模式是這樣一種設計模式。一個被稱作被觀察者的對象,維護一組被稱為觀察者的對象,這些對象依賴于被觀察者,被觀察者自動將自身的狀態的任何變化通知給它們。 當一個被觀察者需要將一些變化通知給觀察者的時候,它將采用廣播的方式,這條廣播可能包含特定于這條通知的一些數據。 當特定的觀察者不再需要接受來自于它所注冊的被觀察者的通知的時候,被觀察者可以將其從所維護的組中刪除。 在這里提及一下設計模式現有的定義很有必要。這個定義是與所使用的語言無關的。通過這個定義,最終我們可以更深層次地了解到設計模式如何使用以及其優勢。在四人幫的《設計模式:可重用的面向對象軟件的元素》這本書中,是這樣定義觀察者模式的: 一個或者更多的觀察者對一個被觀察者的狀態感興趣,將自身的這種興趣通過附著自身的方式注冊在被觀察者身上。當被觀察者發生變化,而這種便可也是觀察者所關心的,就會產生一個通知,這個通知將會被送出去,最后將會調用每個觀察者的更新方法。當觀察者不在對被觀察者的狀態感興趣的時候,它們只需要簡單的將自身剝離即可。 我們現在可以通過實現一個觀察者模式來進一步擴展我們剛才所學到的東西。這個實現包含一下組件: * 被觀察者:維護一組觀察者, 提供用于增加和移除觀察者的方法。 * 觀察者:提供一個更新接口,用于當被觀察者狀態變化時,得到通知。 * 具體的被觀察者:狀態變化時廣播通知給觀察者,保持具體的觀察者的信息。 * 具體的觀察者:保持一個指向具體被觀察者的引用,實現一個更新接口,用于觀察,以便保證自身狀態總是和被觀察者狀態一致的。 首先,讓我們對被觀察者可能有的一組依賴其的觀察者進行建模: ~~~ function ObserverList(){ this.observerList = []; } ObserverList.prototype.Add = function( obj ){ return this.observerList.push( obj ); }; ObserverList.prototype.Empty = function(){ this.observerList = []; }; ObserverList.prototype.Count = function(){ return this.observerList.length; }; ObserverList.prototype.Get = function( index ){ if( index > -1 && index < this.observerList.length ){ return this.observerList[ index ]; } }; ObserverList.prototype.Insert = function( obj, index ){ var pointer = -1; if( index === 0 ){ this.observerList.unshift( obj ); pointer = index; }else if( index === this.observerList.length ){ this.observerList.push( obj ); pointer = index; } return pointer; }; ObserverList.prototype.IndexOf = function( obj, startIndex ){ var i = startIndex, pointer = -1; while( i < this.observerList.length ){ if( this.observerList[i] === obj ){ pointer = i; } i++; } return pointer; }; ObserverList.prototype.RemoveAt = function( index ){ if( index === 0 ){ this.observerList.shift(); }else if( index === this.observerList.length -1 ){ this.observerList.pop(); } }; // Extend an object with an extension function extend( extension, obj ){ for ( var key in extension ){ obj[key] = extension[key]; } } ~~~ 接著,我們對被觀察者以及其增加,刪除,通知在觀察者列表中的觀察者的能力進行建模: ~~~ function Subject(){ this.observers = new ObserverList(); } Subject.prototype.AddObserver = function( observer ){ this.observers.Add( observer ); }; Subject.prototype.RemoveObserver = function( observer ){ this.observers.RemoveAt( this.observers.IndexOf( observer, 0 ) ); }; Subject.prototype.Notify = function( context ){ var observerCount = this.observers.Count(); for(var i=0; i < observerCount; i++){ this.observers.Get(i).Update( context ); } }; ~~~ 我們接著定義建立新的觀察者的一個框架。這里的update 函數之后會被具體的行為覆蓋。 ~~~ // The Observer function Observer(){ this.Update = function(){ // ... }; } ~~~ 在我們的樣例應用里面,我們使用上面的觀察者組件,現在我們定義: * 一個按鈕,這個按鈕用于增加新的充當觀察者的選擇框到頁面上 * 一個控制用的選擇框 , 充當一個被觀察者,通知其它選擇框是否應該被選中 * 一個容器,用于放置新的選擇框 我們接著定義具體被觀察者和具體觀察者,用于給頁面增加新的觀察者,以及實現更新接口。通過查看下面的內聯的注釋,搞清楚在我們樣例中的這些組件是如何工作的。 ### HTML ~~~ <button id="addNewObserver">Add New Observer checkbox</button> <input id="mainCheckbox" type="checkbox"/> <div id="observersContainer"></div> ~~~ ### Sample script ~~~ // 我們DOM 元素的引用 var controlCheckbox = document.getElementById( "mainCheckbox" ), addBtn = document.getElementById( "addNewObserver" ), container = document.getElementById( "observersContainer" ); // 具體的被觀察者 //Subject 類擴展controlCheckbox 類 extend( new Subject(), controlCheckbox ); //點擊checkbox 將會觸發對觀察者的通知 controlCheckbox["onclick"] = new Function( "controlCheckbox.Notify(controlCheckbox.checked)" ); addBtn["onclick"] = AddNewObserver; // 具體的觀察者 function AddNewObserver(){ //建立一個新的用于增加的checkbox var check = document.createElement( "input" ); check.type = "checkbox"; // 使用Observer 類擴展checkbox extend( new Observer(), check ); // 使用定制的Update函數重載 check.Update = function( value ){ this.checked = value; }; // 增加新的觀察者到我們主要的被觀察者的觀察者列表中 controlCheckbox.AddObserver( check ); // 將元素添加到容器的最后 container.appendChild( check ); } ~~~ 在這個例子里面,我們看到了如何實現和配置觀察者模式,了解了被觀察者,觀察者,具體被觀察者,具體觀察者的概念。 ## 觀察者模式和發布/訂閱模式的不同 觀察者模式確實很有用,但是在javascript時間里面,通常我們使用一種叫做發布/訂閱模式的變體來實現觀察者模式。這兩種模式很相似,但是也有一些值得注意的不同。 觀察者模式要求想要接受相關通知的觀察者必須到發起這個事件的被觀察者上注冊這個事件。 發布/訂閱模式使用一個主題/事件頻道,這個頻道處于想要獲取通知的訂閱者和發起事件的發布者之間。這個事件系統允許代碼定義應用相關的事件,這個事件可以傳遞特殊的參數,參數中包含有訂閱者所需要的值。這種想法是為了避免訂閱者和發布者之間的依賴性。 這種和觀察者模式之間的不同,使訂閱者可以實現一個合適的事件處理函數,用于注冊和接受由發布者廣播的相關通知。 這里給出一個關于如何使用發布者/訂閱者模式的例子,這個例子中完整地實現了功能強大的publish(), subscribe() 和 unsubscribe()。 ~~~ // 一個非常簡單的郵件處理器 // 接受的消息的計數器 var mailCounter = 0; // 初始化一個訂閱者,這個訂閱者監聽名叫"inbox/newMessage" 的頻道 // 渲染新消息的粗略信息 var subscriber1 = subscribe( "inbox/newMessage", function( topic, data ) { // 日志記錄主題,用于調試 console.log( "A new message was received: ", topic ); // 使用來自于被觀察者的數據,用于給用戶展示一個消息的粗略信息 $( ".messageSender" ).html( data.sender ); $( ".messagePreview" ).html( data.body ); }); // 這是另外一個訂閱者,使用相同的數據執行不同的任務 // 更細計數器,顯示當前來自于發布者的新信息的數量 var subscriber2 = subscribe( "inbox/newMessage", function( topic, data ) { $('.newMessageCounter').html( mailCounter++ ); }); publish( "inbox/newMessage", [{ sender:"hello@google.com", body: "Hey there! How are you doing today?" }]); // 在之后,我們可以讓我們的訂閱者通過下面的方式取消訂閱來自于新主題的通知 // unsubscribe( subscriber1, ); // unsubscribe( subscriber2 ); ~~~ 這個例子的更廣的意義是對松耦合的原則的一種推崇。不是一個對象直接調用另外一個對象的方法,而是通過訂閱另外一個對象的一個特定的任務或者活動,從而在這個任務或者活動出現的時候的得到通知。 ### 優勢 觀察者和發布/訂閱模式鼓勵人們認真考慮應用不同部分之間的關系,同時幫助我們找出這樣的層,該層中包含有直接的關系,這些關系可以通過一些列的觀察者和被觀察者來替換掉。這中方式可以有效地將一個應用程序切割成小塊,這些小塊耦合度低,從而改善代碼的管理,以及用于潛在的代碼復用。 使用觀察者模式更深層次的動機是,當我們需要維護相關對象的一致性的時候,我們可以避免對象之間的緊密耦合。例如,一個對象可以通知另外一個對象,而不需要知道這個對象的信息。 兩種模式下,觀察者和被觀察者之間都可以存在動態關系。這提供很好的靈活性,而當我們的應用中不同的部分之間緊密耦合的時候,是很難實現這種靈活性的。 盡管這些模式并不是萬能的靈丹妙藥,這些模式仍然是作為最好的設計松耦合系統的工具之一,因此在任何的JavaScript 開發者的工具箱里面,都應該有這樣一個重要的工具。 ### 缺點 事實上,這些模式的一些問題實際上正是來自于它們所帶來的一些好處。在發布/訂閱模式中,將發布者共訂閱者上解耦,將會在一些情況下,導致很難確保我們應用中的特定部分按照我們預期的那樣正常工作。 例如,發布者可以假設有一個或者多個訂閱者正在監聽它們。比如我們基于這樣的假設,在某些應用處理過程中來記錄或者輸出錯誤日志。如果訂閱者執行日志功能崩潰了(或者因為某些原因不能正常工作),因為系統本身的解耦本質,發布者沒有辦法感知到這些事情。 另外一個這種模式的缺點是,訂閱者對彼此之間存在沒有感知,對切換發布者的代價無從得知。因為訂閱者和發布者之間的動態關系,更新依賴也很能去追蹤。 ## 發布/訂閱實現 發布/訂閱在JavaScript的生態系統中非常合適,主要是因為作為核心的ECMAScript 實現是事件驅動的。尤其是在瀏覽器環境下更是如此,因為DOM使用事件作為其主要的用于腳本的交互API。 也就是說,無論是ECMAScript 還是DOM都沒有在實現代碼中提供核心對象或者方法用于創建定制的事件系統(DOM3 的CustomEvent是一個例外,這個事件綁定在DOM上,因此通常用處不大)。 幸運的是,流行的JavaScript庫例如dojo, jQuery(定制事件)以及YUI已經有相關的工具,可以幫助我們方便的實現一個發布/訂閱者系統。下面我們看一些例子。 ~~~ // 發布 // jQuery: $(obj).trigger("channel", [arg1, arg2, arg3]); $( el ).trigger( "/login", [{username:"test", userData:"test"}] ); // Dojo: dojo.publish("channel", [arg1, arg2, arg3] ); dojo.publish( "/login", [{username:"test", userData:"test"}] ); // YUI: el.publish("channel", [arg1, arg2, arg3]); el.publish( "/login", {username:"test", userData:"test"} ); // 訂閱 // jQuery: $(obj).on( "channel", [data], fn ); $( el ).on( "/login", function( event ){...} ); // Dojo: dojo.subscribe( "channel", fn); var handle = dojo.subscribe( "/login", function(data){..} ); // YUI: el.on("channel", handler); el.on( "/login", function( data ){...} ); // 取消訂閱 // jQuery: $(obj).off( "channel" ); $( el ).off( "/login" ); // Dojo: dojo.unsubscribe( handle ); dojo.unsubscribe( handle ); // YUI: el.detach("channel"); el.detach( "/login" ); ~~~ 對于想要在vanilla Javascript(或者其它庫)中使用發布/訂閱模式的人來講, AmplifyJS 包含了一個干凈的,庫無關的實現,可以和任何庫或者工具箱一起使用。[Radio.js](http://radio.uxder.com/),?[PubSubJS](https://github.com/mroderick/PubSubJS)?或者 Pure JS PubSub 來自于?[Peter Higgins](https://github.com/phiggins42/bloody-jquery-plugins/blob/55e41df9bf08f42378bb08b93efcb28555b61aeb/pubsub.js)?都有類似的替代品值得研究。 尤其對于jQuery 開發者來講,他們擁有很多其它的選擇,可以選擇大量的良好實現的代碼,從Peter Higgins 的jQuery插件到Ben Alman 在GitHub 上的(優化的)發布/訂閱 jQuery gist。下面給出了這些代碼的鏈接。 * [Ben Alman的發布/訂閱 gist](https://gist.github.com/661855)(推薦) * [Rick Waldron 在上面基礎上修改的 jQuery-core 風格的實現](https://gist.github.com/705311) * [Peter Higgins 的插件](http://github.com/phiggins42/bloody-jquery-plugins/blob/master/pubsub.js) * [AppendTo 在AmplifyJS中的 發布/訂閱實現](http://amplifyjs.com/) * [Ben Truyman的 gist](https://gist.github.com/826794) 從上面我們可以看到在javascript中有這么多種觀察者模式的實現,讓我們看一下最小的一個版本的發布/訂閱模式實現,這個實現我放在github 上,叫做pubsubz。這個實現展示了發布,訂閱的核心概念,以及如何取消訂閱。 我之所以選擇這個代碼作為我們例子的基礎,是因為這個代碼緊密貼合了方法簽名和實現方式,這種實現方式正是我想看到的javascript版本的經典的觀察者模式所應該有的樣子。 ### 發布/訂閱實例 ~~~ var pubsub = {}; (function(q) { var topics = {}, subUid = -1; // Publish or broadcast events of interest // with a specific topic name and arguments // such as the data to pass along q.publish = function( topic, args ) { if ( !topics[topic] ) { return false; } var subscribers = topics[topic], len = subscribers ? subscribers.length : 0; while (len--) { subscribers[len].func( topic, args ); } return this; }; // Subscribe to events of interest // with a specific topic name and a // callback function, to be executed // when the topic/event is observed q.subscribe = function( topic, func ) { if (!topics[topic]) { topics[topic] = []; } var token = ( ++subUid ).toString(); topics[topic].push({ token: token, func: func }); return token; }; // Unsubscribe from a specific // topic, based on a tokenized reference // to the subscription q.unsubscribe = function( token ) { for ( var m in topics ) { if ( topics[m] ) { for ( var i = 0, j = topics[m].length; i < j; i++ ) { if ( topics[m][i].token === token) { topics[m].splice( i, 1 ); return token; } } } } return this; }; }( pubsub )); ~~~ ### 示例:使用我們的實現 我們現在可以使用發布實例和訂閱感興趣的事件,例如: ~~~ // Another simple message handler // A simple message logger that logs any topics and data received through our // subscriber var messageLogger = function ( topics, data ) { console.log( "Logging: " + topics + ": " + data ); }; // Subscribers listen for topics they have subscribed to and // invoke a callback function (e.g messageLogger) once a new // notification is broadcast on that topic var subscription = pubsub.subscribe( "inbox/newMessage", messageLogger ); // Publishers are in charge of publishing topics or notifications of // interest to the application. e.g: pubsub.publish( "inbox/newMessage", "hello world!" ); // or pubsub.publish( "inbox/newMessage", ["test", "a", "b", "c"] ); // or pubsub.publish( "inbox/newMessage", { sender: "hello@google.com", body: "Hey again!" }); // We cab also unsubscribe if we no longer wish for our subscribers // to be notified // pubsub.unsubscribe( subscription ); // Once unsubscribed, this for example won't result in our // messageLogger being executed as the subscriber is // no longer listening pubsub.publish( "inbox/newMessage", "Hello! are you still there?" ); ~~~ ### 例如:用戶界面通知 接下來,讓我們想象一下,我們有一個Web應用程序,負責顯示實時股票信息。 應用程序可能有一個表格顯示股票統計數據和一個計數器顯示的最后更新點。當數據模型發生變化時,應用程序將需要更新表格和計數器。在這種情況下,我們的主題(這將發布主題/通知)是數據模型以及我們的訂閱者是表格和計數器。 當我們的訂閱者收到通知:該模型本身已經改變,他們自己可以進行相應的更新。 在我們的實現中,如果發現新的股票信息是可用的,我們的訂閱者將收聽到的主題“新數據可用”。如果一個新的通知發布到該主題,那將觸發表格去添加一個包含此信息的新行。它也將更新最后更新計數器,記錄最后一次添加的數據 ~~~ // Return the current local time to be used in our UI later getCurrentTime = function (){ var date = new Date(), m = date.getMonth() + 1, d = date.getDate(), y = date.getFullYear(), t = date.toLocaleTimeString().toLowerCase(); return (m + "/" + d + "/" + y + " " + t); }; // Add a new row of data to our fictional grid component function addGridRow( data ) { // ui.grid.addRow( data ); console.log( "updated grid component with:" + data ); } // Update our fictional grid to show the time it was last // updated function updateCounter( data ) { // ui.grid.updateLastChanged( getCurrentTime() ); console.log( "data last updated at: " + getCurrentTime() + " with " + data); } // Update the grid using the data passed to our subscribers gridUpdate = function( topic, data ){ if ( data !== "undefined" ) { addGridRow( data ); updateCounter( data ); } }; // Create a subscription to the newDataAvailable topic var subscriber = pubsub.subscribe( "newDataAvailable", gridUpdate ); // The following represents updates to our data layer. This could be // powered by ajax requests which broadcast that new data is available // to the rest of the application. // Publish changes to the gridUpdated topic representing new entries pubsub.publish( "newDataAvailable", { summary: "Apple made $5 billion", identifier: "APPL", stockPrice: 570.91 }); pubsub.publish( "newDataAvailable", { summary: "Microsoft made $20 million", identifier: "MSFT", stockPrice: 30.85 }); ~~~ 樣例:在下面這個電影評分的例子里面,我們使用Ben Alman的發布/訂閱實現來解耦應用程序。我們使用Ben Alman的jQuery實現,來展示如何解耦用戶界面。請注意,我們如何做到提交一個評分,來產生一個發布信息,這個信息表明了當前新的用戶和評分數據可用。 剩余的工作留給訂閱者,由訂閱者來代理這些主題中的數據發生的變化。在我們的例子中,我們將新的數據壓入到現存的數組中,接著使用Underscore庫的template()方法來渲染模板。 ### HTML/模板 ~~~ <script id="userTemplate" type="text/html"> <li><%= name %></li> </script> <script id="ratingsTemplate" type="text/html"> <li><strong><%= title %></strong> was rated <%= rating %>/5</li> </script> <div id="container"> <div class="sampleForm"> <p> <label for="twitter_handle">Twitter handle:</label> <input type="text" id="twitter_handle" /> </p> <p> <label for="movie_seen">Name a movie you've seen this year:</label> <input type="text" id="movie_seen" /> </p> <p> <label for="movie_rating">Rate the movie you saw:</label> <select id="movie_rating"> <option value="1">1</option> <option value="2">2</option> <option value="3">3</option> <option value="4">4</option> <option value="5" selected>5</option> </select> </p> <p> <button id="add">Submit rating</button> </p> </div> <div class="summaryTable"> <div id="users"><h3>Recent users</h3></div> <div id="ratings"><h3>Recent movies rated</h3></div> </div> </div> ~~~ ### JavaScript ~~~ ;(function( $ ) { // Pre-compile templates and "cache" them using closure var userTemplate = _.template($( "#userTemplate" ).html()), ratingsTemplate = _.template($( "#ratingsTemplate" ).html()); // Subscribe to the new user topic, which adds a user // to a list of users who have submitted reviews $.subscribe( "/new/user", function( e, data ){ if( data ){ $('#users').append( userTemplate( data )); } }); // Subscribe to the new rating topic. This is composed of a title and // rating. New ratings are appended to a running list of added user // ratings. $.subscribe( "/new/rating", function( e, data ){ var compiledTemplate; if( data ){ $( "#ratings" ).append( ratingsTemplate( data ); } }); // Handler for adding a new user $("#add").on("click", function( e ) { e.preventDefault(); var strUser = $("#twitter_handle").val(), strMovie = $("#movie_seen").val(), strRating = $("#movie_rating").val(); // Inform the application a new user is available $.publish( "/new/user", { name: strUser } ); // Inform the app a new rating is available $.publish( "/new/rating", { title: strMovie, rating: strRating} ); }); })( jQuery ); ~~~ 樣例:解耦一個基于Ajax的jQuery應用。 在我們最后的例子中,我們將從實用的角度來看一下如何在開發早起使用發布/訂閱模式來解耦代碼,這樣可以幫助我們避免之后痛苦的重構過程。 在Ajax重度依賴的應用里面,我們常會見到這種情況,當我們收到一個請求的響應之后,我們希望能夠完成不僅僅一個特定的操作。我們可以簡單的將所有請求后的邏輯加入到成功的回調函數里面,但是這樣做有一些問題。 高度耦合的應用優勢會增加重用功能的代價,因為高度耦合增加了內部函數/代碼的依賴性。這意味著如果我們只是希望獲取一次性獲取結果集,可以將請求后 的邏輯代碼 硬編碼在回調函數里面,這種方式可以正常工作,但是當我們想要對相同的數據源(不同的最終行為)做更多的Ajax調用的時候,這種方式就不適合了,我們必須要多次重寫部分代碼。與其回溯調用相同數據源的每一層,然后在將它們泛化,不如一開始就使用發布/訂閱模式來節約時間。 使用觀察者,我們可以簡單的將整個應用范圍的通知進行隔離,針對不同的事件,我們可以把這種隔離做到我們想要的粒度上,如果使用其它模式,則可能不會有這么優雅的實現。 注意我們下面的例子中,當用戶表明他們想要做一次搜索查詢的時候,一個話題通知就會生成,而當請求返回,并且實際的數據可用的時候,又會生成另外一個通知。而如何使用這些事件(或者返回的數據),都是由訂閱者自己決定的。這樣做的好處是,如果我們想要,我們可以有10個不同的訂閱者,以不同的方式使用返回的數據,而對于Ajax層來講,它不會關心你如何處理數據。它唯一的責任就是請求和返回數據,接著將數據發送給所有想要使用數據的地方。這種相關性上的隔離可以是我們整個代碼設計更為清晰。 ### HTML/Templates ~~~ <form id="flickrSearch"> <input type="text" name="tag" id="query"/> <input type="submit" name="submit" value="submit"/> </form> <div id="lastQuery"></div> <div id="searchResults"></div> <script id="resultTemplate" type="text/html"> <% _.each(items, function( item ){ %> <li><p><img src="<%= item.media.m %>"/></p></li> <% });%> </script> ~~~ ### JavaScript ~~~ ;(function( $ ) { // Pre-compile template and "cache" it using closure var resultTemplate = _.template($( "#resultTemplate" ).html()); // Subscribe to the new search tags topic $.subscribe( "/search/tags" , function( tags ) { $( "#searchResults" ) .html(" <p> Searched for:<strong>" + tags + "</strong> </p> "); }); // Subscribe to the new results topic $.subscribe( "/search/resultSet" , function( results ){ $( "#searchResults" ).append(resultTemplate( results )); }); // Submit a search query and publish tags on the /search/tags topic $( "#flickrSearch" ).submit( function( e ) { e.preventDefault(); var tags = $(this).find( "#query").val(); if ( !tags ){ return; } $.publish( "/search/tags" , [ $.trim(tags) ]); }); // Subscribe to new tags being published and perform // a search query using them. Once data has returned // publish this data for the rest of the application // to consume $.subscribe("/search/tags", function( tags ) { $.getJSON( "http://api.flickr.com/services/feeds/photos_public.gne?jsoncallback=?" ,{ tags: tags, tagmode: "any", format: "json" }, function( data ){ if( !data.items.length ) { return; } $.publish( "/search/resultSet" , data.items ); }); }); })(); ~~~ 觀察者模式在應用設計中,解耦一系列不同的場景上非常有用,如果你沒有用過它,我推薦你嘗試一下今天提到的之前寫到的某個實現。這個模式是一個易于學習的模式,同時也是一個威力巨大的模式。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看