亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 編程 > Ruby > 正文

Ruby設計模式編程之適配器模式實戰攻略

2020-02-24 15:36:20
字體:
來源:轉載
供稿:網友

在實際的軟件系統設計和開發中,為了完成一項工作,我們需要購買第三方庫來加速開發,這會導致應用程序中設計的功能接口與第三方提供的接口不一致,本文是武林技術頻道小編為大家帶來的Ruby設計模式編程之適配器模式實戰攻略,一起來看看吧!

適配器模式
適配器模式可以用于對不同的接口進行包裝以及提供統一的接口,或者是讓某一個對象看起來像是另一個類型的對象。在靜態類型的編程語言里,我們經常使用它去滿足類型系統的特點,但是在類似Ruby這樣的弱類型編程語言里,我們并不需要這么做。盡管如此,它對于我們來說還是有很多意義的。
當使用第三方類或者庫的時候,我們經常從這個例子開始(start out fine):

ruby.html="" tags="">ruby;">def find_nearest_restaurant(locator) locator.nearest(:restaurant, self.lat, self.lon)end

我們假設有一個針對locator的接口,但是如果我們想要find_nearest_restaurant能夠支持另一個庫呢?這個時候我們可能就會去嘗試添加新的特殊的場景的處理:

def find_nearest_restaurant(locator) if locator.is_a? GeoFish  locator.nearest(:restaurant, self.lat, self.lon) elsif locator.is_a? ActsAsFound  locator.find_food(:lat => self.lat, :lon => self.lon) else  raise NotImplementedError, "#{locator.class.name} is not supported." endend

這是一個比較務實的解決方案。或許我們也不再需要考慮去支持另一個庫了。也或許find_nearest_restaurant就是我們使用locator的唯一場景。
那假如你真的需要去支持一個新的locator,那又會是怎么樣的呢?那就是你有三個特定的場景。再假如你需要實現find_nearest_hospital方法呢?這樣你就需要在維護這三種特定的場景時去兼顧兩個不同的地方。當你覺得這種解決方案不再可行的時候,你就需要考慮適配器模式了。
在這個例子中,我們可以為GeoFish以及ActsAsFound編寫適配器,這樣的話,在我們的其他代碼中,我們就不需要了解我們當前正在使用的是哪個庫了:

def find_nearest_hospital(locator) locator.find :type => :hospital,        :lat => self.lat,        :lon => self.lonendlocator = GeoFishAdapter.new(geo_fish_locator)find_nearest_hospital(locator)

特意假設的例子就到此為止,接下來讓我們看看真實的代碼。

實例
今天一大早,你的leader就匆匆忙忙跑過來找到你:“快,快,緊急任務!最近ChinaJoy馬上就要開始了,老板要求提供一種直觀的方式,可以查看到我們新上線的游戲中每個服的在線人數?!?br>你看了看日期,不是吧!這哪里是馬上要開始了,分明是已經開始了!這怎么可能來得及呢?
“沒關系的?!蹦愕膌eader安慰你道:“功能其實很簡單的,接口都已經提供好了,你只需要調用一下就行了。”
好吧,你勉為其難地接受了,對于這種突如其來的新需求,你早已習慣。
你的leader向你具體描述了一下需求,你們的游戲目前有三個服,一服已經開放一段時間了,二服和三服都是新開的服。設計的接口非常輕便,你只需要調用Utility.online_player_count(Fixnum),傳入每個服對應的數值就可以獲取到相應服在線玩家的數量了,如一服傳入1,二服傳入2,三服則傳入3。如果你傳入了一個不存在的服,則會返回-1。然后你只要將得到的數據拼裝成XML就好,具體的顯示功能由你的leader來完成。
好吧,聽起來功能并不是很復雜,如果現在就開始動工好像還來得及,于是你馬上敲起了代碼。
首先定義一個用于統計在線人數的父類PlayerCount,代碼如下:

class PlayerCount    def server_name     raise "You should override this method in subclass."   end      def player_count     raise "You should override this method in subclass."   end  end 

接著定義三個統計類繼承PlayerCount,分別對應了三個不同的服,如下所示:

class ServerOne < PlayerCount    def server_name     "一服"   end      def player_count     Utility.online_player_count(1)   end  end class ServerTwo < PlayerCount    def server_name     "二服"   end      def player_count     Utility.online_player_count(2)   end  end class ServerThree < PlayerCount    def server_name     "三服"   end      def player_count     Utility.online_player_count(3)   end  end 

然后定義一個XMLBuilder類,用于將各服的數據封裝成XML格式,代碼如下:

class XMLBuilder    def self.build_xml player     builder = ""     builder << "<root>"     builder << "<server>" << player.server_name << "</server>"     builder << "<player_count>" << player.player_count.to_s << "</player_count>"     builder << "</root>"   end  end 

這樣的話,所有代碼就完工了,如果你想查看一服在線玩家數只需要調用:

XMLBuilder.build_xml(ServerOne.new) 

查看二服在線玩家數只需要調用:

XMLBuilder.build_xml(ServerTwo.new) 

查看三服在線玩家數只需要調用:

XMLBuilder.build_xml(ServerThree.new) 

咦?你發現查看一服在線玩家數的時候,返回值永遠是-1,查看二服和三服都很正常。
你只好把你的leader叫了過來:“我感覺我寫的代碼沒有問題,但是查詢一服在線玩家數總是返回-1,為什么會這樣呢?”
“哎呀!”你的leader猛然想起,“這是我的問題,前面沒跟你解釋清楚。由于我們的一服已經開放一段時間了,查詢在線玩家數量的功能早就有了,使用的是ServerFirst這個類。當時寫Utility.online_player_count()這個方法主要是為了針對新開的二服和三服,就沒把一服的查詢功能再重復做一遍。這種情況下可以使用適配器模式,這個模式就是為了解決接口之間不兼容的問題而出現的。”
其實適配器模式的使用非常簡單,核心思想就是只要能讓兩個互不兼容的接口能正常對接就行了。上面的代碼中,XMLBuilder中使用PlayerCount來拼裝XML,而ServerFirst并沒有繼承PlayerCount,這個時候就需要一個適配器類來為XMLBuilder和ServerFirst之間搭起一座橋梁,毫無疑問,ServerOne就將充當適配器類的角色。修改ServerOne的代碼,如下所示:

class ServerOne < PlayerCount    def initialize     @serverFirst = ServerFirst.new   end    def server_name     "一服"   end      def player_count     @serverFirst.online_player_count   end  end 

?
這樣通過ServerOne的適配,XMLBuilder和ServerFirst之間就成功完成對接了!使用的時候我們甚至無需知道有ServerFirst這個類,只需要正常創建ServerOne的實例就行了。
需要值得注意的一點是,適配器模式不并是那種會讓架構變得更合理的模式,更多的時候它只是充當救火隊員的角色,幫助解決由于前期架構設計不合理導致的接口不匹配的問題。更好的做法是在設計的時候就盡量把以后可能出現的情況多考慮一些,在這個問題上不要向你的leader學習。

MultiJSON
ActiveSupport在做JSON格式的解碼時,用到的是MultiJSON,這是一個針對JSON庫的適配器。每一個庫都能夠解析JSON,但是做法卻不盡相同。讓我們分別看看針對oj和yajl的適配器。 (提示: 可在命令行中輸入qw multi_json查看源碼。)

module MultiJson module Adapters  class Oj < Adapter   #...   def load(string, options={})    options[:symbol_keys] = options.delete(:symbolize_keys)    ::Oj.load(string, options)   end   #...

Oj的適配器修改了options哈希表,使用Hash#delete將:symbolize_keys項轉換為Oj的:symbol_keys項:

options = {:symbolize_keys => true}options[:symbol_keys] = options.delete(:symbolize_keys) # => trueoptions                         # => {:symbol_keys=>true}

接下來MultiJSON調用了::Oj.load(string, options)。MultiJSON適配后的API跟Oj原有的API非常相似,在此不必贅述。不過你是否注意到,Oj是如何引用的呢?::Oj引用了頂層的Oj類,而不是MultiJson::Adapters::Oj。
現在讓我們看看MultiJSON又是如何適配Yajl庫的:

module MultiJson module Adapters  class Yajl < Adapter   #...   def load(string, options={})    ::Yajl::Parser.new(:symbolize_keys => options[:symbolize_keys]).parse(string)   end   #...

這個適配器從不同的方式實現了load方法。Yajl的方式是先創建一個解析器的實力,然后將傳入的字符串string作為參數調用Yajl::Parser#parse方法。在options哈希表上的處理也略有不同。只有:symbolize_keys項被傳遞給了Yajl。
這些JSON的適配器看似微不足道,但是他們卻可以讓你隨心所欲地在不同的庫之間進行切換,而不需要在每一個解析JSON的地方更新代碼。
ActiveRecord
很多JSON庫往往都遵從相似的模式,這讓適配工作變得相當輕松。但是如果你是在處理一些更加復雜的情況時,結果會是怎樣?ActiveRecord包含了針對不同數據庫的適配器。盡管PostgreSQL和MySQL都是SQL數據庫,但是他們之間還是有很多不同之處,而ActiveRecord通過使用適配器模式屏蔽了這些不同。(提示: 命令行中輸入qw activerecord查看ActiveRecord的代碼)
打開ActiveRecord代碼庫中的lib/connection_adapters目錄,里邊會有針對PostgreSQL,MySQL以及SQLite的適配器。除此之外,還有一個名為AbstractAdapter的適配器,它作為每一個具體的適配器的基類。AbstractAdapter實現了在大部分數據庫中常見的功能,這些功能在其子類比如PostgreSQLAdapter以及AbstractMysqlAdapter中被重新定制,而其中AbstractMysqlAdapter則是另外兩個不同的MySQL適配器——MysqlAdapter以及Mysql2Adapter——的父類。讓我們通過一些真實世界中的例子來看看他們是如何一起工作的。
PostgreSQL和MySQL在SQL方言的實現稍有不同。查詢語句SELECT * FROM users在這兩個數據庫都可以正常執行,但是它們在一些類型的處理上會稍顯不同。在MySQL和PostgreSQL中,時間格式就不盡相同。其中,PostgreSQL支持微秒級別的時間,而MySQL只是到了最近的一個穩定發布的版本中才支持。那這兩個適配器又是如何處理這種差異的呢?
ActiveRecord通過被混入到AbstractAdapter的ActiveRecord::ConnectionAdapters::Quoting中的quoted_date引用日期。而AbstractAdapter中的實現僅僅只是格式化了日期:

def quoted_date(value) #... value.to_s(:db)end

Rails中的ActiveSupport擴展了Time#to_s,使其能夠接收一個代表格式名的符號類型參數。:db所代表的格式就是%Y-%m-%d %H:%M:%S:

# Examples of common formats:Time.now.to_s(:db)   #=> "2014-02-19 06:08:13"Time.now.to_s(:short)  #=> "19 Feb 06:08"Time.now.to_s(:rfc822) #=> "Wed, 19 Feb 2014 06:08:13 +0000"

MySQL的適配器都沒有重寫quoted_date方法,它們自然會繼承這種行為。另一邊,PostgreSQLAdapter則對日期的處理做了兩個修改:

def quoted_date(value) result = super if value.acts_like?(:time) && value.respond_to?(:usec)  result = "#{result}.#{sprintf("%06d", value.usec)}" end if value.year < 0  result = result.sub(/^-/, "") + " BC" end resultend

它在一開始便調用super方法,所以它也會得到一個類似MySQL中格式化后的日期。接下來,它檢測value是否像是一個具體時間。這是一個ActiveSupport中擴展的方法,當一個對象類似Time類型的實例時,它會返回true。這讓它更容易表明各種對象已被假設為類似Time的對象。(提示: 對acts_like?方法感興趣?請在命令行中執行qw activesupport,然后閱讀core_ext/object/acts_like.rb)
第二部分的條件檢查value是否有用于返回毫秒的usec方法。如果可以求得毫秒數,那么它將通過sprintf方法被追加到result字符串的末尾。跟很多時間格式一樣,sprintf也有很多不同的方式用于格式化數字:

sprintf("%06d", 32) #=> "000032"sprintf("%6d", 32) #=> "  32"sprintf("%d",  32) #=> "32"sprintf("%.2f", 32) #=> "32.00"

最后,假如日期是一個負數,PostgreSQLAdapter就會通過加上”BC”去重新格式化日期,這是PostgreSQL數據庫的實際要求:

SELECT '2000-01-20'::timestamp;-- 2000-01-20 00:00:00SELECT '2000-01-20 BC'::timestamp;-- 2000-01-20 00:00:00 BCSELECT '-2000-01-20'::timestamp;-- ERROR: time zone displacement out of range: "-2000-01-20"

這只是ActiveRecord適配多個API時的一個極小的方式,但它卻能幫助你免除由于不同數據庫的細節所帶來的差異和煩惱。
另一個體現SQL數據庫的不同點是數據庫表被創建的方式。MySQL以及PostgreSQL中對主鍵的處理各不相同:

# AbstractMysqlAdapterNATIVE_DATABASE_TYPES = { :primary_key => "int(11) DEFAULT NULL auto_increment PRIMARY KEY", #...}# PostgreSQLAdapterNATIVE_DATABASE_TYPES = { primary_key: "serial primary key", #...}

這兩種適配器都能夠明白ActiveRecord中的主鍵的表示方式,但是它們會在創建新表的時候將此翻譯為不同的SQL語句。當你下次在編寫一個migration或者執行一個查詢的時候,思考一下ActiveRecord的適配器以及它們為你做的所有微小的事情。
DateTime和Time
當MultiJson以及ActiveRecord實現了傳統的適配器的時候,Ruby的靈活性使得另一種解決方案成為可能。DateTime以及Time都用于表示時間,但是它們在內部的處理上是不同的。雖然有著這些細微的差異,但是它們所暴露出來的API卻是極其類似的(提示:命令行中執行qw activesupport查看此處相關代碼):

t = Time.nowt.day   #=> 19     (Day of month)t.wday  #=> 3     (Day of week)t.usec  #=> 371552   (Microseconds)t.to_i  #=> 1392871392 (Epoch secconds)d = DateTime.nowd.day   #=> 19     (Day of month)d.wday  #=> 3     (Day of week)d.usec  #=> NoMethodError: undefined method `usec'd.to_i  #=> NoMethodError: undefined method `to_i'

ActiveSupport通過添加缺失的方法來直接修改DateTime和Time,進而抹平了兩者之間的差異。從實例上看,這里就有一個例子演示了ActiveSupport如何定義DateTime#to_i:

class DateTime def to_i  seconds_since_unix_epoch.to_i end def seconds_since_unix_epoch  (jd - 2440588) * 86400 - offset_in_seconds + seconds_since_midnight end def offset_in_seconds  (offset * 86400).to_i end def seconds_since_midnight  sec + (min * 60) + (hour * 3600) endend

每一個用于支持的方法,seconds_since_unix_epoch,offset_in_seconds,以及seconds_since_midnight都使用或者擴展了DateTime中已經存在的API去定義與Time中匹配的方法。
假如說我們前面所看到的適配器是相對于被適配對象的外部適配器,那么我們現在所看到的這個就可以被稱之為內部適配器。與外部適配器不同的是,這種方法受限于已有的API,并且可能導致一些麻煩的矛盾問題。舉例來說,DateTime和Time在一些特殊的場景下就有可能出現不一樣的行為:

datetime == time #=> truedatetime + 1   #=> 2014-02-26 07:32:39time + 1     #=> 2014-02-25 07:32:40

當加上1的時候,DateTime加上了一天,而Time則是加上了一秒。當你需要使用它們的時候,你要記住ActiveSupport基于這些不同,提供了諸如change和Duration等保證一致行為的方法或類。
這是一個好的模式嗎?它理所當然是方便的,但是如你剛才所見,你仍舊需要注意其中的一些不同之處。
總結
設計模式不是只需要Java,Rails使用設計模式為JSON解析和數據庫維護提供統一的接口,由于Ruby的靈活性,可以直接修改諸如datetime和time之類的類,以提供類似的接口。

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表

圖片精選

亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
国产一区二区在线免费| 国产精品99导航| 国产亚洲精品久久| 亚洲精品影视在线观看| 久久人人看视频| 欧美福利小视频| 久久综合伊人77777| 日韩在线视频免费观看| 国产国语刺激对白av不卡| 中文字幕不卡在线视频极品| 国产精品中文字幕在线观看| 一区二区三区亚洲| 欧洲成人在线视频| 久久99国产综合精品女同| 亚洲精品一区久久久久久| 日本成人在线视频网址| 伊人久久大香线蕉av一区二区| 亚洲日本中文字幕| 精品国产一区久久久| 一区二区三区视频在线| 亚洲网站在线播放| 日韩av在线一区二区| 在线观看免费高清视频97| 亚洲精品影视在线观看| 97精品国产97久久久久久春色| 91在线观看欧美日韩| 美女扒开尿口让男人操亚洲视频网站| 亚洲精品一区二区网址| 亚洲aa在线观看| 国产精品吴梦梦| 中文字幕免费精品一区| 另类图片亚洲另类| 91欧美精品午夜性色福利在线| 国产精品扒开腿爽爽爽视频| 亚洲欧美激情四射在线日| 国产精品入口福利| 日韩av高清不卡| 国产精品亚洲第一区| 国产精品扒开腿爽爽爽视频| 亚洲国产精品久久久| 日本国产一区二区三区| 日韩一级黄色av| 色婷婷综合久久久久| 在线国产精品视频| 97精品一区二区三区| 国产一区二区日韩| 黑人与娇小精品av专区| 久久91超碰青草是什么| 欧美日韩亚洲网| 久久久久久久久久久人体| 欧美亚洲一级片| 久久99热精品| 国内久久久精品| 国产精品久久久久av| 欧美麻豆久久久久久中文| 久久久久国产视频| 成人在线激情视频| 国产999精品视频| 欧美国产日韩视频| 国产视频精品xxxx| 国产成人精品午夜| 成人午夜高潮视频| 欧美激情久久久久久| 91亚洲国产成人久久精品网站| 中文字幕综合一区| 亚洲a∨日韩av高清在线观看| 国产精品热视频| 亚洲精品综合精品自拍| 91国内揄拍国内精品对白| 欧美刺激性大交免费视频| 日韩激情av在线免费观看| 国产伦精品一区二区三区精品视频| 日韩视频欧美视频| 久久91精品国产91久久久| 亚洲高清福利视频| 少妇高潮久久久久久潘金莲| 欧美日韩美女在线观看| 欧美日韩亚洲网| 欧美性感美女h网站在线观看免费| 欧美国产一区二区三区| 亚洲最大福利视频| 国产一区二区丝袜高跟鞋图片| 亚洲伊人久久综合| 久久久久久国产精品| 2019中文字幕在线免费观看| 久久久精品国产网站| 久久久免费精品| 欧美极品欧美精品欧美视频| 国产精品久久久久久av福利| 韩国视频理论视频久久| 精品久久久久久久久久久久久久| 欧美日韩成人在线观看| 亚洲理论电影网| 91久久中文字幕| 日韩**中文字幕毛片| 粗暴蹂躏中文一区二区三区| 日韩女优人人人人射在线视频| 亚洲va欧美va在线观看| 日韩av影院在线观看| 日韩精品在线免费播放| 91豆花精品一区| 国产亚洲aⅴaaaaaa毛片| 久久精品美女视频网站| 77777少妇光屁股久久一区| 亚洲高清一区二| 亚洲女性裸体视频| 欧美黑人狂野猛交老妇| 亚洲精品视频在线观看视频| 国产午夜精品全部视频在线播放| 97在线看福利| 欧美精品videosex性欧美| 尤物九九久久国产精品的分类| 国产精品中文字幕在线观看| 欧美成人自拍视频| 亚洲色图第一页| 国产精品综合久久久| 97成人超碰免| 国产精品免费一区二区三区都可以| 欧美日韩不卡合集视频| 在线观看成人黄色| 亚洲精品日韩欧美| 26uuu亚洲国产精品| 中文字幕免费国产精品| 欧美成人精品一区二区三区| 91精品免费久久久久久久久| 免费不卡欧美自拍视频| 欧美日本国产在线| 麻豆国产精品va在线观看不卡| 97国产一区二区精品久久呦| 国产精品com| 国产欧美va欧美va香蕉在线| 国产精品草莓在线免费观看| 日韩在线观看成人| 欧美天天综合色影久久精品| 97精品久久久中文字幕免费| 亚洲电影免费观看高清| 亚洲欧美变态国产另类| 中文字幕精品一区久久久久| 成人美女免费网站视频| 亚洲国产欧美一区二区三区同亚洲| 国产亚洲精品久久久久久牛牛| 国产精品久久久久9999| 国产精品久久久久久久久久久久| 国产精品福利在线观看网址| 亚洲成人精品久久| 亚洲人成在线免费观看| 国产视频精品免费播放| 国外成人免费在线播放| 日本高清视频一区| 精品久久香蕉国产线看观看gif| 欧洲精品在线视频| 国产一区二区成人| 91中文字幕在线| 国产亚洲aⅴaaaaaa毛片| 日韩成人久久久| 欧美老女人性生活| 欧美在线激情视频| 国产成人精品av在线| 91美女片黄在线观看游戏| 另类少妇人与禽zozz0性伦| 欧美精品少妇videofree| 精品女厕一区二区三区| 日韩在线视频中文字幕| 亚洲欧美在线x视频|