ruby 状态转移
0. 引言
man=Human.new
man.feed
man.fall_in_love # Error
man.help_people
man.fall_in_love
如果你调用某个功能时没有完成前面的事情,就像上面的例子这样,一个人尚未学会帮助他人的人,我们是不希望他去恋爱的,这样一个不懂得互助互爱的人怎么可能珍惜自己的爱人呢?
1. 目标
class Human
include State
def feed
puts"feed myself"
end
def protect_env
puts "protect environment"
end
def help_people
puts "help other people"
end
def fall_in_love
puts "love someone"
end
define_chain :feed,[:protect_env,:help_people],:fall_in_love
end
如代码所示,我希望在我使用的类中包含一个State模块,然后用define_chain定义一个方法链,那么方法链中的方法,必须要在前一个方法调用过之后才可以被调用,否则就会抛出异常。另外,在定义方法链的define_chain中,我希望可以包含列表,列表中的方法需要至少被调用一种才能执行方法链的后续调用。
2. 环绕别名
define_method "#{method}_in_chain" do |*params,&block|
validate_state_for method.to_sym
self.send "#{method}_out_chain",*params,&block
update_state_for method.to_sym
end
alias_method "#{method}_out_chain",method
alias_method method,"#{method}_in_chain"
这部分代码就是define_chain方法的主体,这样,在定义了状态转移方法链之后,直接调用在方法链中的方法,就会自动使用validate_state_for方法检查方法是否可以被调用,在完成调用后使用update_state_for方法更新状态。
module State
def define_chain(*args)
end def validate_state_for(method)
end def update_state_for(method)
end
end
好吧,问题的最关键部分解决了,但还是有一些细节,不要小看细节,它决定成败。
3. 类扩展混入
class Human
extend State
end
但问题来了,我只希望define_chain被作为类方法混入,而validate_state_for和update_state_for方法仍然需要作为类实例方法。那么直接混入肯定就不行了,这时就需要使用ruby另一个魔法了——类扩展混入,将部分方法作为类方法混入,部分方法作为实例方法混入。这种魔法使用了included钩子。
module State
def self.included(base)
base.extend StateMaker
end
module StateMaker
def define_chain(*args)
end
end
def validate_state_for(method)
end
def update_state_for(method)
end
end
现在,在使用下面的方法混入,就获得了我想要的效果。我能够用类方法define_chain定义状态方法链,也能够实例化Human对象调用它的validate_state_for实例方法。
class Human
include State
end
4. 最后一步,实现
module State
def validate_state_for(method)
raise "State is too low to execute #{method}" unless min_state_for(method) <= state
end
def min_state_for(method)
self.class.state_chain.find_index{|k,v| v.include? method}
end
def update_state_for(method)
@_state_from_object_monitor_+=1 if min_state_for(method) == state
end
def reset_state
@_state_from_object_monitor_=0
end
def state
@_state_from_object_monitor_=0 unless @_state_from_object_monitor_
@_state_from_object_monitor_
end
end
该模块还提供了reset_state方法重置状态值。另外,min_state_for方法用于获取调用某个方法的最低状态值,该方法中实际上也使用了ruby一点点小魔法,类实例变量,state_chain是一个类方法,它获取了是我们定义的状态转移方法链的一个hash表,该表是一个类实例变量,这个hash具体结构马上就会看到。
module State
module StateMaker
def define_chain(*args)
args.map{|x| x}
args.flatten.each do |method|
define_method "#{method}_in_chain" do |*params,&block|
validate_state_for method.to_sym
self.send "#{method}_out_chain",*params,&block
update_state_for method.to_sym
nil
end
alias_method "#{method}_out_chain",method
alias_method method,"#{method}_in_chain"
end
@chain_methods=args.each_with_index.inject({}) do |memo,(v,index)|
memo[index]=v.class==Symbol ? [v] : v
memo
end
nil
end def state_chain
@chain_methods
end
end
end
define_chain方法的前半部分使用环绕别名来包裹特定方法,后半部分就是生成方法链的hash表,生成的hash表被保存在实例变量@chain_methods中,由于define_chain被作为类方法混入,所以它自然也成为了混入类的类实例变量,注意,尽量多使用类实例变量而不要使用类变量。而state_chain方法也同时混入成为类方法,该方法纯粹就是用来获取类实例变量chain_methods的。如1.目标中的方法链生成的hash表的结构是:
{0=>[:feed], 1=>[:protect_env, :help_people], 2=>[:fall_in_love]}
5. 结尾
6. 附录
module State
def self.included(base)
base.extend StateMaker
end
module StateMaker
def define_chain(*args)
args.map{|x| x}
args.flatten.each do |method|
define_method "#{method}_in_chain" do |*params,&block|
validate_state_for method.to_sym
result=self.send "#{method}_out_chain",*params,&block
update_state_for method.to_sym
result
end
alias_method "#{method}_out_chain",method
alias_method method,"#{method}_in_chain"
end
@chain_methods=args.each_with_index.inject({}) do |memo,(v,index)|
memo[index]=v.class==Symbol ? [v] : v
memo
end
nil
end
def state_chain
@chain_methods
end
end
def validate_state_for(method)
raise "State is too low to execute #{method}" unless min_state_for(method) <= state
end
def min_state_for(method)
self.class.state_chain.find_index{|k,v| v.include? method}
end
def update_state_for(method)
@_state_from_object_monitor_+=1 if min_state_for(method) == state
end
def reset_state
@_state_from_object_monitor_=0
end
def state
@_state_from_object_monitor_=0 unless @_state_from_object_monitor_
@_state_from_object_monitor_
end
end
ruby 状态转移的更多相关文章
- Rest(表述性状态转移)
本文的主要内容有: 1.了解Rest 2.了解RESTful WebService 3.使用SpringMvc实现RESTful ------------------------------我是华丽的 ...
- 【分布式协调器】Paxos的工程实现-Cocklebur状态转移
集群中的主机经过选举过程由Looking状态变为了Leadering或Following状态.而这些状态之间转移的条件是什么呢?先来个直观的,上状态图. 图 4.1 Cocklebur选举过程中的状态 ...
- 背包DP 存在异或条件的状态转移问题
题目链接 分析:有大佬说可以用线性基写,可惜我不会,这是用DP写的 题目明确说明可到达的位置只与能值有关,和下标无关,我们就可以排个序,这样每个数可以转移的区间就是它的所有后缀 我们可以用dp[i][ ...
- 读懂TCP状态转移
读懂TCP状态转移过程,对理解网络编程颇有帮助,本文将对TCP状态转移过程进行介绍,但各状态(总共11个)含义不在本文介绍的范围,请参考文末的书目列表. TCP状态转换图(state transiti ...
- 状压dp终极篇(状态转移的思想)
状压dp是将每种状态都压缩成用一个二进制串,然后利用位运算进行操作的dp,而凡是dp都需要进行状态转移 对于简单的dp问题只需要一个二维数组dp[ i ][ j ]就能解决 具体操作为首先把状态压缩为 ...
- 动态规划:HDU1160-FatMouse's Speed(记录动态规划状态转移过程)
FatMouse's Speed Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others) ...
- poj 2778 DNA Sequence 状态及状态转移 AC自动机 矩阵快速幂
题目链接 题意 给定\(m\)个字符串,问长度为\(n\)的字符串中有多少个不包含那\(m\)个字符串. (字符集为\(A,T,C,G\),\(m\leq 10\),长度\(\leq 10\),\(n ...
- HDU - 1176 免费馅饼 DP多种状态转移
免费馅饼 都说天上不会掉馅饼,但有一天gameboy正走在回家的小径上,忽然天上掉下大把大把的馅饼.说来gameboy的人品实在是太好了,这馅饼别处都不掉,就掉落在他身旁的10米范围内.馅饼如果掉在了 ...
- C/C++用状态转移表联合函数指针数组实现状态机FSM
状态机在project中使用很的频繁,有例如以下常见的三种实现方法: 1. switch-case 实现.适合简单的状态机. 2. 二维状态表state-event实现.逻辑清晰.可是矩阵通常比較稀疏 ...
随机推荐
- React 16 源码瞎几把解读 【前戏】 为啥组件外面非得包个标签?
〇.看前准备 1.自行clone react最新代码 2.自行搭建一个能跑react的test项目 一.看表面:那些插件 如何解析JSX 有如下一段代码: // ---- hearder.jsx 组件 ...
- 一致性hash理解
在做memcached分布式集群时往往要用到一致性hash算法来调节缓存数据的分布. 通常的hash算法是以服务器数量N作为模数,使用key%N的值来获得最终位置,显然当服务器数量发生变化即N发生变化 ...
- mysql 配置数据库主从同步
参考:https://www.cnblogs.com/kevingrace/p/6256603.html http://www.51testing.com/html/00/130600-243651. ...
- Java学习笔记()ArrayList
1.什么是ArrayList ArrayList就是传说中的动态数组,用MSDN中的说法,就是Array的复杂版本,它提供了如下一些好处: 动态的增加和减少元素 实现了ICollection和ILis ...
- [MySQL] specified key was too long max key length is 767bytes
https://blog.csdn.net/u012099869/article/details/53815084/
- 修改 jupyter notebook 启动工作路径的方法
Windows下jupyter notebook默认的启动路径就是当前cmd启动jupyter 的路径: C:\Users\用户名>jupyter notebook 此时jupyter 的启动工 ...
- Three.js基础探寻八——法向材质与材质的纹理贴图
4.法向材质 法向材质可以将材质的颜色设置为其法向量的方向,有时候对于调试很有帮助. 法向材质的设定很简单,甚至不用设置任何参数: new THREE.MeshNormalMaterial() 材质的 ...
- 程序员必备的代码审查(Code Review)清单
在我们关于高效代码审查的博文中,我们建议使用一个检查清单.在代码审查中,检查清单是一个非常好的工具——它们保证了审查可以在你的团队中始终如一的进行.它们也是一种保证常见问题能够被发现并被解决的便利方式 ...
- 洛谷P1438 无聊的数列 [zkw线段树]
题目传送门 无聊的数列 题目背景 无聊的YYB总喜欢搞出一些正常人无法搞出的东西.有一天,无聊的YYB想出了一道无聊的题:无聊的数列...(K峰:这题不是傻X题吗) 题目描述 维护一个数列{a[i]} ...
- 恢复mysql数据库误删数据
前言 某一天,天朗气清:突然传来消息:数据库被删库了!这简直不亚于8级大地震呀:一找原因,服务器宕机造成了数据库数据丢失.于是,通过日志恢复数据的救援开始了. 正文 在数据库开启binlog功能 找到 ...