Day17. Dynamic Programming

看完今天的文章,自己试着在专案写动态写法後,读者们会发现今天讲的东西很实用。我们可以透过动态的写法,省去相当多程序码。

define_method

define_method 是目前最常提到的用法,该用法可以动态的宣告方法

class Person 
  define_method :greeting, -> { puts 'Hello!' }
end

Person.new.greeting # => Hello!

上面的例子中,greeting 等同於以下写法。

class Person 
  def greeting
   puts 'Hello!' 
  end
end

⭐️ 接着我们来说明我在寄信服务上的动态方法

class NotificationMailer < ApplicationMailer
  # 所有方法
  MAIL_METHODS = %w[
    order_init order_need_pay order_canceled_by_customer
    order_canceled_by_user order_canceled_not_pay
    order_shipped order_arrived order_ship_failed
    invoice_issued return_applied return_failed return_partial_failed return_done
    maintain_quoted register member_upgrade member_renewal expiring_accumulate
    member_expiring cart acquire_rebate expiring_credit_point maintain_back
  ]

  MAIL_METHODS.each do |key|
    define_method(key) do |email, instance, title = nil|
      @instance = instance
      mail(to: email, subject: title || send("#{key}_title"))
    end
  end
end

上面为mailer的方法,由於不同的寄信方式内容大致上相同,就可以使用define_method。其中 define_methodemail, instance, title = nil,都是被带入的参数。上述的方式也可以用method_missing实现,method_missing在後面会介绍到,不过这里用define_method会比较直观。

send

在我刚开始使用send的时候,我问自己一个问题?这个问题是Ruby的方法是Symbol吗?上网查了以後,发现很多人都有类似的问题。确实symbolRuby的世界中占取的object_id相同,所以也可以想像得到,我们可以使用send 搭配symbol做动态的呼叫方法

# 意思相同
1 + 1 = 2
1.send(:+, 1)

# 意思相同
"We are the world".upcase
"We are the world".send(:.upcase)

# 使用客制化 send
class Klass
  def hello(*args)
    "Hello " + args.join(' ')
  end
end

k = Klass.new
k.send(:hello, "gentle", "readers")   #=> "Hello gentle readers"

send的第一个参数是方法,第二个以後全部都是带进来的参数。接着我们讲send如何搭配其他方法!

send & respond_to?

send & respond_to?的搭配简直是绝妙。以下的写法A,可以使用写法B代替

print "Search for: "
request = gets.chomp

# 写法A
if request == "writer"
  puts book.writer
elsif request == "press"
  puts book.press
elseif request == "date"
  puts book.date
else
  puts "Input error"
end

# 写法b
if book.respond_to?(request)
  puts book.send(request)
else
  puts "Input error"
end

⭐️ 接着我们看respond_to?的好处?使用respond_to?被使用以後,就不必担心会有undefined method的错误。

obj = Object.new
if obj.respond_to?("talk")
   obj.talk
else
   puts "Sorry, object can't talk!"
end

# 若找不到talk,也不会跳出undefined method 'talk' for #<Object:0x12345678> (NoMethodError)的错误

⭐️ 学习rails的读者们千万要记住, respond_to 是Rails 控制器的用法,和 respond_to? 完全是不一样的概念喔!

def index
  @people = Person.find(:all)

  respond_to do |format|
    format.html
    format.json { render :json => @people.as_json }
  end
end

send & define_method

⭐️ 以下为senddefine_method 的搭配。

class A
  def create_method(name, &block)
    self.class.send(:define_method, name, &block)
  end
end

a = A.new
a.create_method(:run) { "Running Man" }

a.run
#=> "Running Man"

senddefine_method的搭配方法,确实可以玩很多不同的变化 ?,後面介绍的method_missing,就会搭配senddefine_method

return self

回传selfRuby是很常见的写法,此外符合设计流程的Builder Pattern

class Salad  
  def initialize    
    @ingredients = []  
  end  
  
  def add_veg   
    @ingredients << :vegatable 
    
    self  
  end 
end

salad = Salad.new      #=> #<Salad:0x00007fc42151a3a0 @ingredients=[]>
salad.add_veg          #=> #<Salad:0x00007fc42151a3a0 @ingredients=[:vegatable]>
salad.instance_eval {@ingredients}  #=> [:vegatable]

salad.add_veg          #=> #<Salad:0x00007fc42151a3a0 @ingredients=[:vegatable, :vegatable]>
salad.instance_eval {@ingredients}  #=> [:vegatable, :vegatable]
class Stack  
  def initialize    
    @store = Array.new  
  end  

  def push(element)    
    @store.push(element)    
    
    self  
  end
  
  def pop
    @store.pop
    
    self
  end
end

stack = Stack.new
#=> #<Stack:0x00007fc42dd78ab8 @store=[]>
stack.push '1'
#=> #<Stack:0x00007fc42dd78ab8 @store=["1"]>
tack.push '1'
#=> #<Stack:0x00007fc42dd78ab8 @store=["1", "1"]>
stack.push '1'
#=> #<Stack:0x00007fc42dd78ab8 @store=["1", "1", "1"]>
stack.pop
#=> #<Stack:0x00007fc42dd78ab8 @store=["1", "1"]>
stack.pop
#=> #<Stack:0x00007fc42dd78ab8 @store=["1"]>
stack.pop
#=> #<Stack:0x00007fc42dd78ab8 @store=[]>

以上面的例子来讲,我们做了多次push, pop,实体变数删改元素。回传self,可以让我们可以连锁使用push, pop的动作,达到改动object的目的

method_missing

在Ruby当中,这不算是陌生的方法,有时候在报错的时候都会看到。

除了报错以外,我们还可以用method_missing做一些好玩的事情:

class Developer
  def method_missing method, *args, &block
    # 若 prefix 不为 it_day ,则继承父层(预设)的 method_missing ➡️ 报错
    return super method, *args, &block unless method.to_s =~ /^it_\w+/
    
    self.class.send(:define_method, method) do
      "我要挑战IT铁人赛主题为 " + method.to_s.gsub(/^it_/, '').to_s + " 的文章"
    end
    
    self.send method, *args, &block
  end

end

itday = Developer.new
itday.it_rails        #=> "我要挑战IT铁人赛主题为 rails 的文章"
itday.it_javascript   #=> "我要挑战IT铁人赛主题为 javascript 的文章"

结论

当我们在写条件句,或者case when的时候,可以想想可以用什麽动态的写法省去多余的code。Rails 本身是个遵照惯例的框架,所以熟悉一段时间 Rails之後,不难发现其实很多规律性的地方都可以使用动态生成的写法来作替代。

接着我们来复习目前已经讲了几个设计流程

  • Singleton Pattern ➡️ Day12, Day13
  • Strategy, Service Pattern ➡️ Day16
  • Builder Pattern ➡️ 今天

从Day18-22,我们会开始讲解画面。

参考资料


<<:  熟习-使用

>>:  机器学习:深度学习

可视化编程游戏引擎

可视化编程游戏引擎 可视化编程是指可以把程序代码视觉化 不需要写程序代码就可以编写程序逻辑,降低了...

[DAY15]k8s必备良药-Lens

Lens - K8s上好用的client端程序 除了使用kubectl cli去操作k8s外,也有u...

30天程序语言研究

今天是30天程序语言研究的第十八天,由於深度学习老师多让我们上了python的进阶课程里面包括之前没...

Transactions (5-1) - Serializability Isolation - Serial & 2PL

昨天谈到 write skew 和 phantoms ,是 2 种特别难重现的 竞争条件 (race...

Day06 Kibana - Discover Search

在前面我们已经稍微了解如何把资料抓取到Elasticsearch,但是单纯用api来查询资料,对使用...