继续复习 + 笔记, 今天要写的是 Dynamic Classes & Methods

Struct

一般的, 定义一个类的做法如下:

1
2
3
4
5
6
7
8
9
class Game
  attr_accessor :name, :year, :system

  def initialize(name, year, system)
    self.name = name
    self.year = year
    self.system = system
  end
end

而鉴于以上这个例子的数据结构比较简单, 所以其实我们可以使用Struct来写之:

1
2
Game = Struct.new(:name, :year, :system)

如果需要添加实例方法则可以这么干:

1
2
3
4
5
Game = Struct.new(:name, :year, :system) do
  def to_s
    "#{name},#{year},#{system}"
  end
end

如果需要定义的 data 比 behavior 要多的话, 推荐使用 Struct 来创建类, 反之则使用传统的方法.

alias_method
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
class Timeline

  def initialize(tweets=[])
   @tweets = tweets
  end

  def tweets
    @tweets
  end

  def contents
    @tweets
  end

end

由于 tweets 和 contents 两个方法体其实是一样的只是方法名不同, 为免重复我们可以这么干

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
class Timeline
  def initialize(tweets=\[\])
   @tweets = tweets
  end

  def tweets
    @tweets
  end

 #实际上其实这是getter方法,所以其实这里可以这么写

 # attr_reader :tweets

  alias_method :contents, :tweets

end

更多例子, 假设如下在 Timeline 类

1
2
3
4
5
6
7
8
9
class Timeline

  attr_accessor: :tweets

  def print
    puts tweets.join('\n')
  end
end

我们需要给 print 方法添加 authentication .如果由于某些原因我们不想改动现有的方法的话, 可以重新打开Timeline 这个类,然后使用 alias_method

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
class Timeline

  alias_method :old_print, :print

  def print
    authenticate!
    old_print
  end

  #实际上就是旧有的 print 方法改名为 old_print, 而重写了 print 给它加上了 authenticate!
  #然后调用旧有方法 old_print

  def authenticate!
    # do sth authentication
  end
end

但是,需要注意的是,重新打开一个类是个危险的做法. 所以, 另一种做法是使用继承.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
class AuthenticatedTimeline
  def print
    authenticate!
    super
  end

  def authenticate!
    # do sth authentication
  end
end

好吧, 尴尬了,貌似 alias_method 没看到什么更实际的意义了 =.=

define_method

下边例子, 可以看到比较多的重复代码,

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
class Tweet
  def draft
    @status = :draft
  end

  def posted
    @status = :posted
  end

  def deleted
    @status = : deleted
  end

使用 define_method 可以杠杠的减少这些重复. 并且,当需要添加新的 state 时候只需添加到 states 数组中即可.

1
2
3
4
5
6
7
8
9
class Tweet
  states = [:draft, :posted, :deleted]

  states.each do |status|
    define_method status do
      @status = status
    end
  end
end
send
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
class Timeline
  def initialize(tweets)
    @tweets = tweets
  end

  def contents
    @tweets
  end

  private
  def direct_messages
  end
end

tweets = %w{ 'Compiling!' 'Bundling...'}
timeline = Timeline.new(tweets)

timeline.contents 一般的, 我们是这么调用 contents 方法.

除此外,我们还可以使用 send

timeline.send(:contents)

等同于

timeline.send("contents")

另外,我们还可以用 send 来调用 private 或者 protected timeline.direct_message

如果不希望调用 private 和 protected 的方法则可以用 public_send

尴尬,更具体用途有待挖掘.

method 方法
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
class Timeline
  def initialize(tweets)
    @tweets = tweets
  end

  def contents
    @tweets
  end

  def show_tweet(index)
    puts @tweets[index]
  end
end

tweets = ['Compling!', 'Bundling...']
timeline = Timeline.new(tweets)

content_method = timeline.method(:contents)
 # => #<Method: Timeline#contents>

content_method.call
 # => ['Compling!', 'Bundling...']

show_method = timeline.method(:show_tweet)
 # => #<Method: Timeline#show_tweet>

show_method.call(0)
 # => Compiling!

(0..1).each(&show_method)
 # =>
 # Compiling!
 # Bundling...

 # 通过 & 把 method 对象转换成 block
 # same as
 # show_method.call(0)
 # show_method.call(1)

在 Ruby 中, 任何东西都是 object, 任意的一个方法同样,也是一个 object

Practice

重构

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
class Library
  attr_accessor :games

  def each(&block)
    games.each(&block)
  end

  def map(&block)
    games.map(&block)
  end

  def select(&block)
    games.select(&block)
  end
end

好吧,没掌握熟练,各种转晕了