设计流程的出现,让我们可以写出一套好的流程,并且帮助团队少写多余的程序码。由於Ruby不像Javascript,是标准的物件导向语言,当然也可以使用各种形式的设计流程。今天,我们会介绍常用、漂亮的设计流程。
我们先举以下为寄发简讯的 Message
服务为例
class Sms::SendMessage
def self.call(phone, code: nil)
new(phone, code).call
end
def initialize(phone, code)
# @host, @username, @password 为存在专案内部的加密变数
@host = Rails.application.credentials.sms[:host]
@username = Rails.application.credentials.sms[:username]
@password = Rails.application.credentials.sms[:password]
# @dstaddr: 电话号码
# @smbody: 简讯欸荣
@dstaddr = phone
@smbody = "#{code} is your verification code."
end
attr_reader :host, :username, :password, :dstaddr, :smbody
def call
ApiClient.post host, headers: nil, payload: { username: username, password: password, dstaddr: dstaddr, smbody: smbody }
end
end
其中ApiClient
在Day12提到过,是专门打API用的类别方法。
Sms::SendMessage
使用方式如下,第一个参数为欲寄发简讯的电话号码,第二个参数为验证码。
Sms::SendMessage.call('0983168969', code: '123456')
只要填妥两个参数,就可以发简讯了!
我们习惯使用call
表示我们要呼叫该参数,而call
里面包含呼叫实体变数,而这种行为我们称为委托delegate
。将self.new(*params).call
的行为委托给call
去执行的设计流程,称为Service Pattern
def self.call(phone, code: nil)
# self.new(phone, code).call 的简写
new(phone, code).call
end
像这种单一class
负责单一职责的设计模式Service Pattern
, 是Rails常见的设计模式之一。
自己曾做过的电商专案中,除了简讯发送Service以外,还有以下这些 Service。
第三方相关
金流相关
绿界付款
绿界退款
绿界开立发票
绿界折让发票
绿界查询付款状态
物流相关
顺丰物流下单
顺丰物流查询货态
简讯相关
发送验证码
电商相关
付款动作
退款动作
POS给点动作
这些Service的目的只为单一职责(一次只做一件事)。举例来说因此若排程端、後台使用者端、顾客端需要做退款动作的时候,只需要呼叫退款流程 service
即可。而这些Service
与MVC
核心架构耦合性越低,重复使用的可能性跟便利性就更高,在同个专案所使用的Service 多和 ActiveRecord
耦合,若要成为给大家复用的Gem
,那就必须写出耦合性更低的程序码。
举简单的例子讲解Service Pattern
class AppleService
def self.call(*args, &block)
new(*args).call(&block)
end
def initialize(a, b = nil, options = {})
@a, @b,@c = a, b, options[:c]
end
attr_reader :a, :b, :c
def call
p "=== what is a: #{a} ==="
p "=== what is b: #{b} ==="
p "=== what is c: #{c} ==="
if block_given?
yield(a, b, c)
else
{a: a, b: b, c: c}
end
end
end
至於如何使用参数,我们在Day11提及过,这里就先简单举例AppleService
带入不同参数的结果。
AppleService.call("a") #=> {:a=>"a", :b=>nil, :c=>nil}
AppleService.call("a", c: 1) #=> {:a=>"a", :b=>{:c=>1}, :c=>nil}
AppleService.call("a", nil, c: 1) #=> {:a=>"a", :b=>nil, :c=>1}
我们也可以搭配 block
使用(Block 的使用可以参考Day9、Day10)
以下区块负责的内容是将收到的三个值用斜线接起来再回传,区块是个有趣的章节,推荐读者在学Rails
的时候切万不要退避三舍
AppleService.call("a", "b", c: 1) {|x,y,z| [x,y,z].join('/')} #=> "a/b/1"
当我们对某物件新增单体方法,只有该物件有独特技能,而若将某类别新增单体方法的话,则为类别方法。Day12、 Day13 提过,这边就不细谈
这是我个人常使用的Pattern
之一,不管是打Api、串接金流、串接物流、汇入表单等变化性很低(偶尔才有变化)的动作,都可以用Strategy Pattern
来去实现!Strategy Pattern
可以用来设定一套流程,我们举一个不存取值的例子来讲:
module Strategy
DEFAULT_STRATEGY = {
params: -> { p '组成资料' },
perform: -> { p '打Api' },
receiver: -> { p '成功或失败' },
handler: -> { p '取得 response payload 并处理' }
}
def get_sty_flow
block_given? ? yield(DEFAULT_STRATEGY) : DEFAULT_STRATEGY
end
def sty_flow
get_sty_flow.map do |k, v|
# 先找有没有前缀为sty的方法
go_method = "sty_#{k}".to_sym
# 没有的话取得 get_sty_flow 的 key
go_method = v if !self.respond_to?(go_method)
next unless go_method.present?
# 判断是否是Proc,若不是则视为 method
if go_method.is_a? Proc
go_method.call
else
self.send(go_method)
end
# 回传key(无意义)
k
end
end
end
看到DEFAULT_STRATEGY
对应 Proc,就有一种写Javascript
function可以当作变数的感觉。
说明:
1. 流程会写在 get_sty_flow 方法内,而DEFAULT_STRATEGY 为预设的流程
2. 执行流程的地方在 sty_flow
3. DEFAULT_STRATEGY 的value可以是Proc,也可以是symbol
接着我们介绍以下三种情境。
情境A ➡️ 使用预设的Strategy
class A
include Strategy
end
A.new.sty_flow
#
#======== 印出以下资讯 ========#
#
# "组成资料",
# "打Api",
# "成功或失败",
# "取得 response payload 并处理"
#
#=> [:params, :perform, :receiver, :handler]
情境B ➡️ 使用继承,增添客制化的流程processor
class B
include Strategy
def get_sty_flow
super do |strategy|
{ **strategy, processor: -> {'资料处理'} }
end
end
end
B.new.sty_flow
#
#======== 印出以下资讯 ========#
#
# "组成资料",
# "打Api",
# "成功或失败",
# "取得 response payload 并处理"
#
#=> [:params, :perform, :receiver, :handler, :processor]
情境C ➡️ 使用客制化的方法 sty_params
class C
include Strategy
def sty_params
p '在C组成资料,而非在预设流程'
end
end
C.new.sty_flow
#
#======== 印出以下资讯 ========#
#
# "在C组成资料,而非在预设流程",
# "打Api",
# "成功或失败",
# "取得 response payload 并处理"
#
#=> [:params, :perform, :receiver, :handler]
我们可以发现上面的方法是真的都可以被抽换的,符合 Strategy Pattern 的精神,也很Gurustrategy
网站所使用的icon
。
其实还有很多设计流程没有讲到,但碍於篇幅的关系,今天先讲三个。在Day32讲解摊提时,我们会介绍另一个设计流程Decorator Pattern
。
Class 系列到此告一个段落,明天开始讲Dynamic Programming
<<: 无线上网:Wi-Fi, 3G, 4G 及 5G 都是些是什麽?
DAY9 MongoDB 文件与嵌入式(巢状)文件查询(Find) Find 把 MongoDB 的...
在之前 interface 那篇文章, 认识到可以使用 Index Signatures, 发现他...
这几天写 String methods 的时候,在句法里发现(regexp)这个词,查了一下原来是 ...
本篇大纲:d3.zoom( )、zoom 旗下的API、范例 上一篇看完让人烧脑的Force之後,...
之前范例执行结束如上所示,倘若我们希望点选学生即展开该学生成绩怎麽做? Grid 显示 Detai...