ruby和javascript的策略模式

来自官方

策略模式定义了一系列的算法,并将每一个算法封装起来,而且使它们还可以相互替换。策略模式让算法独立于使用它的客户而独立变化。

结构图

ruby中的简单实现(代码来自《ruby设计模式》)

# 根据鸭子模型, 没有让策略对象继承一个提供统一接口的基类
# 策略1
class 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>'
  end
end

# 策略2
class PlainTextFormatter
  def output_report title, text
    puts '******** ' + title + ' ********'
    text.each do |line|
      puts line
    end
  end
end

# 这边之所以用策略模式来做,是因为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
  end
end

# 环境对象调用不同的策略
Reporter.new(HTMLFormatter.new).output_report
Reporter.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

# 策略1
class IsNonEmpty
  def self.check(data)
    data.nil?
  end

  def self.notice
    "传入的值不能为空\n"
  end
end

# 策略2
class IsAlphaNum
  def self.check(data)
    data =~ /[^a-z0-9]/
  end

  def self.notice
    "传入的值只能保护字母和数字,不能包含特殊字符\n"
  end
end

# 环境
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?
  end
end

validate = Validate.new

hash_data = {
    first: {type: IsNonEmpty, data: 1324},
    second: {type: IsNonEmpty, data: nil},
    third: {type: IsAlphaNum, data: '123angel'},
    forth: {type: IsAlphaNum, data: '123sf@/!'}
}

validate.check(hash_data)
if validate.errors
  puts validate.msg
end

最后: 本打算总结出ruby和javascript共享接口的不同, 奈何能力有限,只能意会出来却不能言传出来
也可能是为没有理解出其中的关键点

posted @ 2015-04-03 13:53  耿小曾  阅读(331)  评论(0编辑  收藏  举报