標籤:
來自官方
策略模式定義了一系列的演算法,並將每一個演算法封裝起來,而且使它們還可以相互替換。策略模式讓演算法獨立於使用它的客戶而獨立變化。
結構圖
ruby中的簡單實現(代碼來自《ruby設計模式》)
# 根據鴨子模型, 沒有讓策略對象繼承一個提供統一介面的基類# 策略1class HTMLFormatter def output_report title, text puts ‘<html>‘ puts ‘ <head>‘ puts ‘ <title>‘ + title + ‘</title>‘ puts ‘ </head>‘ puts ‘ <body>‘ text.each do |line| puts "<p>#{line}</p>" end puts ‘ </body>‘ puts ‘</html>‘ endend# 策略2class PlainTextFormatter def output_report title, text puts ‘******** ‘ + title + ‘ ********‘ text.each do |line| puts line end endend# 這邊之所以用策略模式來做,是因為Reporter有可能會將text列印成不同格式的文本,如果將來需要# 支援xml那麼只需要增加一個支援output_report介面的類就可以了class Reporter attr_reader :title, :text attr_accessor :formater def initialize formater @title = ‘My Report‘ @text = [‘This is my report‘, ‘Please see the report‘, ‘It is ok‘] @formater = formater end # 策略對象具有共同的介面 def output_report @formater.output_report @title, @text endend# 環境對象調用不同的策略Reporter.new(HTMLFormatter.new).output_reportReporter.new(PlainTextFormatter.new).output_report
那麼策略模式到底可以在什麼時候用呢?
我們先來一個例子,一般情況下,如果我們要做資料合法性驗證,很多時候都是按照swith語句來判斷,但是這就帶來幾個問題,首先如果增加需求的話,我們還要再次修改這段代碼以增加邏輯,而且在進行單元測試的時候也會越來越複雜,代碼如下:
# 在最早的時候我差不多就是用這樣的方式去做表單的資料驗證的validator = { validate: function (value, type) { switch (type) { case ‘isNonEmpty ‘: { return true; // NonEmpty 驗證結果 } case ‘isNumber ‘: { return true; // Number 驗證結果 break; } case ‘isAlphaNum ‘: { return true; // AlphaNum 驗證結果 } default: { return true; } } } }; // 測試 alert(validator.validate("123", "isNonEmpty"));
ok, 用策略模式去改造一下, 首先分析一下validator是一個策略環境,不同的驗證就是一個策略對象,我們需要做的就是讓不同的策略對象支援統一的介面
重構代碼如下:
var validator = { types: {}, messages: [], config: {}, validate: function (data) { var i, msg, type, checker, result_ok; this.messages = []; for (i in data) { if (data.hasOwnProperty(i)) { type = this.config[i]; // 根據key查詢是否有存在的驗證規則 checker = this.types[type]; // 擷取驗證規則的驗證類 if (!type) { continue; // 如果驗證規則不存在,則不處理 } if (!checker) { // 如果驗證規則類不存在,拋出異常 throw { name: "ValidationError", message: "No handler to validate type " + type }; } # 開始執行按需執行不同的策略 result_ok = checker.validate(data[i]); // 使用查到到的單個驗證類進行驗證 if (!result_ok) { msg = "Invalid value for *" + i + "*, " + checker.instructions; this.messages.push(msg); } } } return this.hasErrors(); }, hasErrors: function () { return this.messages.length !== 0; }};// 驗證給定的值是否不為空白validator.types.isNonEmpty = { validate: function (value) { return value !== ""; }, instructions: "傳入的值不可為空"};// 驗證給定的值是否是數字validator.types.isNumber = { validate: function (value) { return !isNaN(value); }, instructions: "傳入的值只能是合法的數字,例如:1, 3.14 or 2010"};// 驗證給定的值是否只是字母或數字validator.types.isAlphaNum = { validate: function (value) { return !/[^a-z0-9]/i.test(value); }, instructions: "傳入的值只能保護字母和數字,不能包含特殊字元"};var data = { first_name: "Tom", last_name: "Xu", age: "unknown", username: "TomXu"};validator.config = { first_name: ‘isNonEmpty‘, age: ‘isNumber‘, username: ‘isAlphaNum‘};validator.validate(data);if (validator.hasErrors()) { console.log(validator.messages.join("\n"));}
早期的時候看到這樣的代碼我只想說fuck, 明明很簡單的東西,非搞的這麼複雜有必要嗎?
當然, 假設項目很簡單,用重構前的代碼OK, 而且這裡也只是去舉個例子
那麼同樣的需求用ruby如何?呢?
代碼如下:(這裡我完全是用javascript的思維來寫ruby了, 不知道閱讀性有沒有問題)
# coding: utf-8# 策略1class IsNonEmpty def self.check(data) data.nil? end def self.notice "傳入的值不可為空\n" endend# 策略2class IsAlphaNum def self.check(data) data =~ /[^a-z0-9]/ end def self.notice "傳入的值只能保護字母和數字,不能包含特殊字元\n" endend# 環境class Validate attr_reader :msg def initialize # @types = [] @msg = ‘‘ # @config = {} end # 這個地方沒有再按照javascript的邏輯去寫,這邊如果需要不同的執行個體支援不同的驗證的話, # 可以將策略對象添加到@types中 # @config 同理 # def add_types(type) # @types << type # end def check(hash_data) hash_data.each_pair do |key, val| @msg << "#{key}errors #{val[:type].notice}" if val[:type].check(val[:data]) end end def errors !@msg.nil? endendvalidate = Validate.newhash_data = { first: {type: IsNonEmpty, data: 1324}, second: {type: IsNonEmpty, data: nil}, third: {type: IsAlphaNum, data: ‘123angel‘}, forth: {type: IsAlphaNum, data: ‘[email protected]/!‘}}validate.check(hash_data)if validate.errors puts validate.msgend
最後: 本打算總結出ruby和javascript共用介面的不同, 奈何能力有限,只能意會出來卻不能言傳出來
也可能是為沒有理解出其中的關鍵點
ruby和javascript的策略模式