Rails ApplicationService为何同时定义execute、call方法及(...)参数含义
关于ApplicationService基类两个设计问题的解答
1. 为什么同时保留execute和call两个类入口方法
这是典型的社区惯例兼容设计,没有特殊魔法:
- Ruby生态里可调用对象的标准入口就是
call方法:Proc、Lambda、Method对象全都是用.call触发执行,服务对象用call做入口的话,既符合Ruby原生的可调用对象约定,还能享受obj.(args)的简写语法糖,甚至可以直接把服务对象当Proc传给枚举方法之类的场景,写法更灵活。 - 而
execute是早期Rails社区推广服务对象模式时,很多教程、老版本第三方服务对象gem约定的入口名,不少早期接触服务对象的开发者已经习惯了写Service.execute(args)的调用形式,项目里也可能存在大量历史遗留的execute调用点。 - 基类同时保留两个入口,本质是降低团队约定成本:不管开发者习惯哪种命名、老代码用的是哪种调用方式,都能正常运行,不需要全局替换调用代码,也不用在代码评审里纠结命名规范问题。注意两个入口不是完全重复的:类方法
execute会调用实例的execute实例方法,类方法call会调用实例的call实例方法,子类实现时只需要写自己选用的那个入口对应的实例方法即可。
2. (...)参数写法的作用与区别
这是Ruby 2.7版本引入的前向参数语法,专门用来简化参数透传的场景,和其他参数写法的差异很明确:
*args只能捕获位置参数,Ruby 3.0之后关键字参数和位置参数严格分离,如果调用时传了关键字参数,*args会把它当成普通Hash位置参数传递,很容易触发参数错误,也没法透传块参数。**kwargs只能捕获关键字参数,传位置参数会直接报错。就算把*args, **kwargs, &block全写上可以覆盖所有参数类型,每次透传都要重复写一遍非常冗余。(...)是语法层面提供的全量透传简写:不管调用时传入的是位置参数、关键字参数还是块参数,都会原封不动透传给后续的方法调用,不会出现参数类型识别错误的问题。
你看到的这段代码:
完全等价于下面的冗余写法,但是简洁很多:def self.call(...) new(...).call end
这个写法的唯一限制是方法内部没法直接获取透传的参数值,如果需要在实例化之前对参数做预处理、校验,就不能用这个简写,还是要手动声明全量参数。def self.call(*args, **kwargs, &block) new(*args, **kwargs, &block).call end
内容的提问来源于stack exchange,提问作者rmcsharry
相关产品推荐
相关产品推荐

