深入 Ruby 17:DSL、instance_eval 与 class_eval:配置怎样获得执行上下文
省略接收者之后,规则仍是 Ruby 程序 系列导读 · 下载本篇完整实验 Rules.build { priority_at_least(2) } 看起来像专门的配置语言,实际仍执行普通 Ruby block。方法能够省略接收者,是因为执行上下文改变了;外部局部变量仍然可以被捕获。这种表达方式没有自动获得安全隔离。 本篇承接作用域与动态方法,在 Ruby 3.4 中比较普通 API、block DSL 和字符串求值。完整实验位于 examples/ruby/labs/17/run.rb。字符串形式只使用源码内固定的合成代码,以说明常量查找差异;外部输入不进入 eval。 123threshold = 2plain = Rules.new.priority_at_least(threshold)dsl = Rules.build { priority_at_least(threshold) } 两种写法应产生相同的筛选行为。DSL 只是调用形式,字段校验与业务语义仍由普通方法负责。若两种入口各写一套验证逻辑,配置语法越简短,后续维护反而越容易...
深入 Ruby 16:动态方法、反射与钩子:元编程怎样保持可诊断
动态接口需要同时回答调用与查询 系列导读 · 下载本篇完整实验 代理对象能执行 open?,却声称自己不响应这个方法,依赖反射的调用者可能直接拒绝使用它。另一个代理把未知方法都返回 nil,拼写错误又会变成正常结果。动态接口除了生成行为,还需要维护可发现性与失败语义。 本篇承接方法查找与委托,在 Ruby 3.4 中比较普通方法、define_method 和 method_missing。完整实验位于 examples/ruby/labs/16/run.rb,动态版本与普通版本使用同一任务输入对照,不通过代码行数判断设计是否更好。 123456789class StaticRules def open?(task) task.fetch(:status) == :open end def done?(task) task.fetch(:status) == :done endend 只有两个状态时,这个实现已经清楚。元编程版用于解释机制;如果状态集合没有显著重复或动态来源,不需要为了“Ruby 风格”替换它。 define_method 从可调用体建立实例方...
深入 Ruby 15:Struct、Data 与模式匹配:数据如何被拆解
不可替换字段,仍可能修改字段里的数组 系列导读 · 下载本篇完整实验 Data.define 生成的数据对象没有普通字段写入器,实例也被冻结,但其中的标签数组仍可能被修改。数据载体、对象不可变性和结构匹配是三项独立能力,不能因为一种类同时提供几种接口就把它们混成一个保证。 本篇承接相等与协议,使用 Ruby 3.4 的 Struct、Data 和模式匹配。完整实验在 examples/ruby/labs/15/run.rb,重点是任务记录的表示与边界验证,不引入外部匹配库。 12345678TaskValue = Data.define(:id, :tags)tags = []task = TaskValue.new(id: 1, tags: tags)tags << :rubyraise unless task.frozen?raise unless task.tags == [:ruby]raise unless task.tags.equal?(tags) 结果说明构造器保存了字段引用。Data 限制字段替换,不递归冻结被引用的对象。若需要完整任务快照,必须在构...
深入 Ruby 14:鸭子类型与委托:不继承也能满足什么契约
有 each 方法,还不等于能遍历任务 系列导读 · 下载本篇完整实验 一个对象的 respond_to?(:each) 返回真,但调用后从不产生元素。代码没有抛异常,任务报告却变成空集合。动态类型允许调用在运行时解析,协议是否满足仍要靠行为定义。 本篇承接 Enumerable 与对象协作,使用 Ruby 3.4 的核心转换协议,建立两种互不继承的任务源。完整实验在 examples/ruby/labs/14/run.rb。关注点是依赖哪些行为,而不是让所有输入先变成同一种类。 12345def ids(source) result = [] source.each { |row| result << row.fetch(:id) } resultend 这个函数要求 source 在每次迭代时传入包含 :id 的行对象。它没有要求 source 继承 Array,也没有要求行对象必须是某个任务类。接口约束由实际调用构成:能调用 each、能接收 block、按约定产生元素,元素支持 fetch(:id)。 两种独立实现共享一个协议 内存源...
深入 Ruby 13:Module、include、prepend 与 refinement:复用影响哪条查找链
日志模块混入之后为何没有执行 系列导读 · 下载本篇完整实验 类中已经定义了 run,再 include 一个同名日志模块,调用仍然直接进入类的方法。改成 prepend 后,日志又会先执行。这一差异来自查找位置,不是 Ruby 猜测哪个方法更像装饰器。 本篇承接单例类与 super,在 Ruby 3.4 中比较组合、混入与词法扩展。独立实验位于 examples/ruby/labs/13/run.rb,用事件数组记录完整顺序。这里只构造本地日志标记,不把输出日志当成外部投递成功的证明。 12345678module Logging def run(events) events << :before result = super events << :after result endend 这个模块没有提供底层工作实现。它依赖查找链后面还有一个兼容的 run,并依赖后续返回值与异常语义符合预期。能成功 include 并不意味着这个协议已经满足。 include 插入复用实现 实验中的 Work#run 记录 :work 并返回...
深入 Ruby 12:单例类、方法查找与 super:调用命中了哪个方法
一个对象的方法可以不同于同类其他对象 系列导读 · 下载本篇完整实验 两个任务处理器都由同一个类创建,其中一个却多了一层日志行为。Ruby 允许只给单个对象定义方法,这个方法不会自动加入所有同类实例。用“类里有什么方法”解释全部调用,因此不够完整。 本篇以前一篇的实例与可见性为基础,使用 Ruby 3.4 的 singleton_class、Method#owner 与 super_method 观察实际分派。实验位于 examples/ruby/labs/12/run.rb。这些接口让方法查找成为可验证的对象关系,而不是凭语法猜测。 123456789101112131415class Worker def run :normal endendspecial = Worker.newordinary = Worker.newdef special.run [:special, super]endp special.runp ordinary.run 结果分别为 [:special, :normal] 和 :normal。special 上的定义遮住了普通类中的同名方...
深入 Ruby 11:类、实例与可见性:对象怎样维护合法状态
属性能够写入,不代表状态转换合法 系列导读 · 下载本篇完整实验 一个任务可以拥有 status 属性,但允许任意 task.status = value,就无法保证状态只在预定路径上改变。构造器验证、实例变量和方法可见性共同决定对象对外承诺,单纯把 Hash 改写成类不会自动产生约束。 本篇承接作用域与对象所有权,在 Ruby 3.4 中建立独立的 Task 实验。任务初始状态为 open,完成后成为 done;重复完成视为调用错误。这是教学领域规则,并非所有任务系统都应该采用的状态机。 123456789101112131415161718class Task attr_reader :title, :status def initialize(title:) raise ArgumentError, 'title' unless title.is_a?(String) && !title.empty? @title = title.dup.freeze @status = :open end def comple...
深入 Ruby 10:身份、相等、复制与冻结:谁共享了可变状态
副本里的修改为何出现在原对象中 系列导读 · 下载本篇完整实验 任务清单调用 dup 后得到一个新数组,但修改副本中的任务标题,原清单可能同时改变。顶层容器确实复制了,内部引用却仍指向原来的对象。判断这个问题需要画出对象与引用,而不能只数变量名。 本篇以前面的集合与作用域为基础,使用 Ruby 3.4 的核心对象接口。完整实验位于 examples/ruby/labs/10/run.rb。对 Taskbook 而言,目标是明确任务数据由谁持有、谁可以修改,以及哪些状态可以稳定地作为 Hash 键。 1234567original = [+'write']copy = original.dupcopy.first << '!'p originalp original.equal?(copy)p original.first.equal?(copy.first) 结果依次是 ["write!"]、false、true。一元加号确保这里使用可变字符串,让实验不受字符串字面量冻结设置干扰。容器身份与元素身份是两项不同...
深入 Ruby 09:作用域、self 与常量:名字怎样绑定到对象
同一个模块名,常量查找却不同 系列导读 · 下载本篇完整实验 把嵌套模块改成带双冒号的写法,文件缩进减少了,程序却可能开始抛出 NameError。这种重构改变的不只是排版,还改变了代码定义处的词法嵌套。理解这一点,需要把局部变量、当前接收者和常量的查找分开讨论。 本篇承接方法、参数和闭包,使用 Ruby 3.4 的核心能力。例子围绕 Taskbook 的命名空间展开,不涉及 Rails 自动加载。完整实验位于 examples/ruby/labs/09/run.rb,其中每个规则都有可失败断言。 12345678910module Taskbook DEFAULT_PRIORITY = 2 module Rules def self.default_priority DEFAULT_PRIORITY end endendp Taskbook::Rules.default_priority 这段代码返回 2。方法定义处嵌套在 Taskbook::Rules 与 Taskbook 中,未限定的常量名能够沿这个定义上下文查找。把内部定义移到顶层写成 mod...
深入 Ruby 08:Enumerable、Enumerator 与 Lazy
筛选、映射和聚合看起来是数组能力,Ruby 也能让自定义任务源获得这些方法。关键是 each 协议。Enumerator 提供另一种控制遍历的方式,Lazy 又把求值推迟到消费时;是否需要全部输入,必须通过需求与调用次数观察。 文章卡 项目 内容 先修 第 04、06–07 篇;集合、yield 与退出目标 实验 bundle exec ruby labs/08/run.rb 核心问题 each 如何支持组合,惰性何时求值,重复消费是否缓存? 验收 自定义集合、100 对 3 的调用计数、重复 force 与有限 take 工程边界 输入可重放性、资源寿命和输出上限必须另行声明 each 是协议的起点 本篇定义一个最小任务流: 12345678910111213class TaskStream include Enumerable def initialize(items) @items = items end def each return enum_for(__method__) unless block_given? ...

