ruby元編程

對(duì)象模型

所有class定義之外的代碼默認(rèn)運(yùn)行在頂級(jí)對(duì)象main中酥泛。

打開(kāi)類

rubyclass更像是一個(gè)作用于操作符而不是類定義語(yǔ)句,它可以創(chuàng)建一個(gè)不存在的類嫌拣,也可以打開(kāi)一個(gè)已定義的類柔袁,然后向內(nèi)添加新的方法和屬性,這種技術(shù)稱為“打開(kāi)類”技術(shù)异逐。
但是注意捶索,當(dāng)打開(kāi)類重新定義新的方法時(shí),如果跟該類已有的方法重名灰瞻,原來(lái)的方法就會(huì)被覆蓋腥例,這稱之為猴子補(bǔ)丁(MonkeyPatch

對(duì)象中有什么

實(shí)例變量

有如下類定義酝润。

class MyClass
    def my_method
        @v = 1
    end
end

java這樣的靜態(tài)語(yǔ)言不同燎竖,ruby對(duì)象的類和實(shí)例變量沒(méi)有關(guān)系,當(dāng)給實(shí)例變量賦值時(shí)他們才會(huì)生成要销,因此如果new一個(gè)MyClass類而沒(méi)有調(diào)用my_method方法构回,就不會(huì)有@v實(shí)例變量,這可以使用@instance_variables方法驗(yàn)證

方法

可以通過(guò)#methods.grep /my/來(lái)查找匹配my的方法
如果可以撬開(kāi)ruby解釋器查看某個(gè)對(duì)象會(huì)發(fā)現(xiàn),對(duì)象其實(shí)并沒(méi)有包含一組方法纤掸,它只包含了實(shí)例變量和一個(gè)對(duì)自身的引用

重訪類

重要概念:類自身也是對(duì)象
類和其他對(duì)象一樣也有自己的類脐供,叫Class(在javaC#中類也是Class類的實(shí)例,Class的類也是Class借跪,自我引用)一個(gè)對(duì)象的方法就是類的實(shí)例方法政己,那么一個(gè)類的方法就是Class的實(shí)例方法

"hello".class #=> String
String.class #=> Class

所有的類都最終繼承于ObjectObject本身又繼承于BasicObject(ruby對(duì)象體系的根節(jié)點(diǎn))

String.superclass #=> Object
Object.superclass #=> BasicObject
BasicObject.superClass #=> nil

類是增加了三個(gè)方法的(new()垦梆、allocate()匹颤、superclass())Module

Class.superclass #=> Module
Module.superclass #=> Object
image

調(diào)用方法時(shí)發(fā)生了什么

方法查找

若有如下定義

Module M
    def my_method
        puts 'my method'
    end
end
  
Class D
    include M
end
  
Class C < D
end
  
C.new.my_method  #可以使用C.new.ancestors打印祖先鏈

當(dāng)調(diào)用my_method方法時(shí)會(huì)依據(jù)下圖的祖先鏈尋找該方法。注意KernelObject中包含的一個(gè)Module托猩,所以所有的類都具有這個(gè)Module中定義的實(shí)例方法(如print
我們也可以利用這種技術(shù)給Kernel增加一個(gè)方法印蓖,這個(gè)內(nèi)核方法就可以為所有對(duì)象所用了。

image

self

每一行代碼都會(huì)在一個(gè)對(duì)象中被執(zhí)行——這個(gè)對(duì)象就是所謂的“當(dāng)前對(duì)象”京腥,當(dāng)前對(duì)象可以用self來(lái)表示和訪問(wèn)赦肃,所有沒(méi)有指明接受者的方法都在self上調(diào)用。
在類定義中當(dāng)前對(duì)象self是在定義的類公浪,同時(shí)self也是當(dāng)前類他宛,在對(duì)象的實(shí)例方法中self就是方法的接收者。

class Test
 puts self   #=> Test 當(dāng)前類
 def foo
   puts self
 end
end
Test.new.foo #=> #<Test:0x007f8bf197b240> 當(dāng)前對(duì)象欠气,方法接收者
代碼 圖例
image
image

左側(cè)的代碼有右側(cè)的祖先鏈厅各,所以Book.new.print 會(huì)調(diào)用printable:print方法。每當(dāng)類包含一個(gè)模塊時(shí)预柒,該模塊會(huì)被插入到祖先鏈中队塘,在類的正上方

代碼 圖例
image
image

Module extend self

extend self的作用就是可以在不include該模塊的情況下按如下方式調(diào)用
一般extend self的模塊會(huì)被當(dāng)做一個(gè)只有類方法的工具類來(lái)使用

module M
 extend self
 def greet
   puts "hi"
 end
end
M.greet  # =>  hi

總結(jié)

image

方法

動(dòng)態(tài)派發(fā)

使用class#send(:method_name, param)調(diào)用方法的好處是可以在代碼運(yùn)行期知道最后一刻才決定調(diào)用那個(gè)方法。
動(dòng)態(tài)定義
使用class#define_method(: method_name, proc)來(lái)定義一個(gè)方法

class MyClass
    define_method :my_method do |arg|
        arg * 3
    end
endMyClass.new.my_method(3) #=> 9

動(dòng)態(tài)刪除

使用class#undef_method刪除方法宜鸯,從而得到一個(gè)白板類憔古。

class MyClass
    undef_method puts
    puts "hello"  #=>  由于刪除了puts方法,該句會(huì)報(bào)錯(cuò)
end

幽靈方法(慎用)

所有在類中沒(méi)定義的方法都會(huì)調(diào)用Kernal中的method_missing()方法淋袖,該方法拋出NoMethodException異常鸿市,可以重寫該方法來(lái)收集那些“迷路“的方法調(diào)用

class MyClass
    def method_missing(method, *args)
        puts "you call: #{method}(#{args.join(',')})"
    end
end

可以定義一個(gè)類,只要用等號(hào)給它賦值一個(gè)屬性即碗,該對(duì)象就會(huì)自動(dòng)擁有該屬性

class No
  def initialize
    @attr = {}
  end
 
  def method_missing(method, *args)
    if method =~ /=$/
      @attr[method.to_s.chop] = args[0]
    else
      @attr[method.to_s]
    end
  end
end
n = No.new
n.name = "lang"
n.age = 27

代碼塊

yield

可以通過(guò)yield將原代碼塊執(zhí)行焰情,并做一些附加操作

class Scanner
  def read_file
    using(f = File.new) {
      f.read(file_path)
      f.close
    }
  end
  # 定義using關(guān)鍵詞即便出現(xiàn)異常也能自動(dòng)關(guān)閉資源
  def using(resource)
    begin
      yield
    rescue
      resource.close
    end
  end
end

作用域

一般情況下
局部變量只能存在當(dāng)前作用域中,也就是說(shuō)局部變量無(wú)法跨作用域調(diào)用剥懒,一般情況下程序會(huì)在以下三個(gè)作用域門關(guān)閉前一個(gè)作用域烙样,同時(shí)打開(kāi)一個(gè)新的作用域
類定義
模塊定義
方法定義
實(shí)例變量會(huì)在實(shí)例的生存周期中都可見(jiàn)

扁平作用域

如果想讓局部變量(綁定)穿過(guò)作用域門,則可以用方法調(diào)用來(lái)替代作用域門蕊肥,具體來(lái)說(shuō)就是用Class.new代替class關(guān)鍵字,Module.new代替module關(guān)鍵字,define_method代替def關(guān)鍵字

count = 1
Counter = Class.new do
  define_method :get_count do
    count
  end
end
 
c = Counter.new
puts c.get_count

#instance_eval

使用該方法傳入一個(gè)代碼塊壁却,塊的接受者會(huì)成為self批狱,因此代碼塊中可以訪問(wèn)接受者的私有方法和實(shí)例變量

class Test
  attr_accessor :name
  def initialize
    @name = "lang"
  end
end
t = Test.new
t.instance_eval do
  @name = "zhou"
end
puts t.name  #=> zhou

可調(diào)用對(duì)象

proc對(duì)象

盡管ruby中絕大部分東西都是對(duì)象,但是塊不是展东,如果想存儲(chǔ)一個(gè)塊供以后使用需要一個(gè)對(duì)象才能做到赔硫,為了解決這個(gè)問(wèn)題,ruby在標(biāo)準(zhǔn)庫(kù)中提供了Proc類盐肃,可以通過(guò)將代碼塊傳給Proc.new()方法來(lái)創(chuàng)建一個(gè)proc爪膊,然后調(diào)用Proc#call來(lái)執(zhí)行,這種技術(shù)稱為延遲執(zhí)行

#第一種方法
inc = Proc.new {|x| x + 1}  #=> Proc對(duì)象
inc.call(2) #=> 3
  
#第二種方法
dec =lambda {|x| x - 1} #=> Proc對(duì)象
dec.call(2) #=> 1

給一個(gè)方法傳入代碼塊的方式有兩種:
第一種砸王,在方法末尾直接聲明代碼塊推盛,然后在方法內(nèi)部通過(guò)yield調(diào)用,但缺點(diǎn)是不能重復(fù)使用yield(因?yàn)槠涫谴a塊而不是對(duì)象)谦铃,比如再將yield傳給方法內(nèi)部的方法

def foo(greeding)
    puts "#{greeding} #{yield}"
end
foo("hello"){"lang"}
# or
foo("hello", &(Proc.new {"lang"}))

第二種耘成,將Proc作為參數(shù)傳入方法中,可以通過(guò)&符號(hào)將Proc對(duì)象轉(zhuǎn)換為代碼塊驹闰,作為最后一個(gè)參數(shù)瘪菌,然后通過(guò)Proc#call調(diào)用
&的真正含義是,這是一個(gè)Proc對(duì)象嘹朗,我想把它當(dāng)做代碼塊來(lái)使用师妙,簡(jiǎn)單的去掉&操作符,就能得到一個(gè)proc對(duì)象

def foo(greeding, &name)
  puts "#{greeding} #{name.call}"
end
 
zhou = Proc.new {"zhou"}
foo("nihao", zhou)

類定義

.class_eval

如果想要在不知道類名的情況下打開(kāi)類并且使用def定義一個(gè)方法屹培,可以使用.class_eval方法默穴,如果想要打開(kāi)一個(gè)對(duì)象(修改self)可以使用#instance_eval方法

def def_method_for_class(a_class)
  a_class.class_eval do
    def say_hi
      puts "hi"
    end
  end
end
 
def_method_for_class(String)
'abc'.say_hi

類實(shí)例變量和類變量

由于類也是對(duì)象,所以在類定義的作用域中如果定義@var(@同self)惫谤,則該變量是類的實(shí)例變量壁顶,類實(shí)例變量不能在實(shí)例方法中調(diào)用

class Test
 @var = 2  #=> 類的實(shí)例變量
 def self.read
    @var
 end
 def set_var
    @var = 1  #=> 對(duì)象的實(shí)例變量
 end
 def get_var
    @var
 end
end
 
t = Test.new
t.set_var
puts Test.read  #=> 2
puts t.get_var  #=> 1

可以使用@@var定義類變量恤煞,類變量可以在實(shí)例方法中訪問(wèn)

class Test
 @@var = 2  #=> 類的實(shí)例變量
 def get_var
    @@var  #=> 2
 end
end

單件方法

只給某個(gè)對(duì)象增加一個(gè)方法验懊,則這個(gè)方法叫做單件方法

str = "nihao"
def str.title?
  if self.upcase == self
end

其實(shí)我們?nèi)粘J褂玫念惙椒ň褪穷悓?shí)例的單件方法妥色,而且其定義方式也跟單件方法一樣

def MyClass.singleton_method; end
def str.singleton_method; end
類宏
如果類MyClass中有一批舊方法如 old_method1, old_method2已經(jīng)棄用仰担,對(duì)其的調(diào)用希望實(shí)際調(diào)用新方法new_method1, new_method2令杈,如何優(yōu)雅的解決锁蠕?
class MyClass
  #def old_method1; puts "old_method1"; end
  #def old_method2; puts "old_method2"; end
  def new_method1; puts "new_method1"; end
  def new_method2; puts "new_method2"; end
  def self.replace_method(old_method, new_method)
    warn "#{old_method}已棄用,請(qǐng)用新方法#{new_method}"
    define_method :old_method do |*args, &block|
      send(new_method, *args, &block)
    end
  end
  replace_method :old_method1, :new_method1  #=> self.replace_method(:old_method1, :new_method1)
  replace_method :old_method2, :new_metho2d
end

用元編程實(shí)現(xiàn)的attr_accessor

class MacroClass
  def self.prop(name)
    define_method name do
      instance_variable_get "@#{name}"
    end
 
    define_method name.to_s + "=" do |value|
      instance_variable_set "@#{name}", value
    end
  end
  prop :name   #=>  等價(jià)于attr_accessor
  def initialize
    @name = "zhou"
  end
end

eigenclass

每個(gè)eigenclass只有一個(gè)實(shí)例并且不能被繼承养葵,它是對(duì)象的單件方法的存活之處入客。在調(diào)用一個(gè)方法時(shí)自阱,接收者會(huì)先查詢eigenclass中有沒(méi)有該方法(單件方法)嚎莉,如果有就直接調(diào)用,如果沒(méi)有就沿著祖先鏈一直向上尋找沛豌。
對(duì)于類的eigenclass就是存放類方法的地方
下圖闡釋了eigenclass在祖先鏈的位置 以#開(kāi)頭的為eigenclass

image

eigenclass的超類就是超類的eigenclass趋箩,有了這種繼承關(guān)系赃额,可以在子類中調(diào)用父類的類方法(因?yàn)?D繼承#C)
一個(gè)對(duì)象的eigenclass類的超類是這個(gè)對(duì)象的類,一個(gè)類的eigenclass的超類是該類的超類的eigenclass
打開(kāi)對(duì)象的eigenclass定義單件方法

# 打開(kāi)obj的enginclass,定義一個(gè)單件方法a_singleton_method
# 如果把obj換成類名,或在類定義中使用 class << self 則打開(kāi)該類的eigenclass添加屬性
class << obj
 def a_singleton_method
   'obj#a_singleton_method'
 end
end

打開(kāi)類的eigenclass定義類方法

# 打開(kāi)obj的enginclass,定義一個(gè)單件方法a_singleton_method
# 如果把obj換成類名,或在類定義中使用 class << self 則打開(kāi)該類的eigenclass添加屬性
class C; end
class << C
 def class_method
   "C.class_method"
 end
end
  
# 或
class C
  class << slef
    def class_method
      "C.class_method"
    end
  end
end

類擴(kuò)展和對(duì)象擴(kuò)展Object#extend

extend關(guān)鍵字可以代替include關(guān)鍵字叫确,用于將模塊混含到類或?qū)ο笾刑迹ㄒ徊煌氖牵褂?code>extend會(huì)使模塊方法變成對(duì)象的單件方法竹勉,或成為類的類方法

module MyModule
  def say_hi; puts "hi" end;
end
 
obj = Object.new
obj.extend MyModule
obj.say_hi  #=>  成為對(duì)象的單件方法
 
class MyClass
  extend MyModule
end
MyClass.say_hi  #=>  成為類方法

方法別名alias和環(huán)繞別名

可以使用alias關(guān)鍵字給方法定義別名

module MyModule
  def say_hi; puts "hi" end;
  alias :hi :say_hi  #=>  注意兩個(gè)方法名之間沒(méi)有逗號(hào)
end

如果想要對(duì)某一個(gè)我們不能修改的庫(kù)方法前后增加額外代碼飞盆,而這個(gè)庫(kù)方法在項(xiàng)目中已經(jīng)用過(guò)無(wú)數(shù)次,我們不能修改每一處調(diào)用該如何處理次乓?
這時(shí)可以使用環(huán)繞別名的技巧吓歇,該技巧的核心是:如果先定義別名再修改方法,則使用別名調(diào)用的時(shí)候還是調(diào)用的老方法票腰,這樣我們就可以先用別名把老方法存下來(lái)城看,然后重新定義這個(gè)方法,加上額外處理的代碼后丧慈,再使用別名調(diào)用老方法

class String
  alias :real_length :length
  def length
    if self.real_length > 5  #=>  使用原來(lái)的方法
      "long"
    else
      "short"
    end
  end
end
puts "abc".real_length #=> 3
puts "abc".length  #=> short

類擴(kuò)展混入

上文說(shuō)到析命,如果想將一個(gè)module中的方法當(dāng)做類的實(shí)例方法包含進(jìn)來(lái),可以使用include關(guān)鍵字
如果想當(dāng)做類方法包含進(jìn)來(lái)逃默,則可以使用extend關(guān)鍵字鹃愤,或者將該模塊包含到類的eigenclass中(class << self; ....; end

class Myclass
  include MyModule  #=>  作為實(shí)例方法包含進(jìn)類
  extend MyModule  #=>  作為類方法包含進(jìn)類
  class << self
    include MyModule  #=>  作為類方法包含進(jìn)類
  end
end
  
module MyModule
  #...
end

但是如果想部分當(dāng)做實(shí)例方法,部分當(dāng)做類方法mixin到類中如何操作呢
module中創(chuàng)建一個(gè)類ClassMethods完域,該類中包含想要定義成類方法的方法
included鉤子方法中使用extend方法將ClassMethods類中的方法包含到包含者的eigenclass中(成為類方法)
ClassMethods類外的方法被include時(shí)還是實(shí)例方法
將該module include到類中

class Myclass
  include MyModule
end
module MyModule
  # 鉤子方法,當(dāng)模塊被混含時(shí)調(diào)用,base為包含模塊的類
  def self.included(base)
    base.extend(ClassMethods)  #=>  extend方法會(huì)把ClassMethod類中的方法包含到base的eigenclass中
  end
 
  # 成為包含者的實(shí)例方法
  def instance_method
  end
 
  # 該類中的方法成為包含者的類方法
  class ClassMethods
    def xxx
      #...
    end
  end
end

測(cè)試

相對(duì)于測(cè)試普通代碼软吐,測(cè)試元編程代碼引入了額外的維度,記住吟税,元編程是編寫代碼的代碼凹耙,因此你可能需要在兩個(gè)層次上測(cè)試它

  • 測(cè)試自己的代碼
  • 測(cè)試這個(gè)代碼所生成的代碼
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市肠仪,隨后出現(xiàn)的幾起案子肖抱,更是在濱河造成了極大的恐慌,老刑警劉巖异旧,帶你破解...
    沈念sama閱讀 216,843評(píng)論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件意述,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡吮蛹,警方通過(guò)查閱死者的電腦和手機(jī)荤崇,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,538評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)潮针,“玉大人术荤,你說(shuō)我怎么就攤上這事∶颗瘢” “怎么了瓣戚?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,187評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵端圈,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我带兜,道長(zhǎng)枫笛,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,264評(píng)論 1 292
  • 正文 為了忘掉前任刚照,我火速辦了婚禮,結(jié)果婚禮上喧兄,老公的妹妹穿的比我還像新娘无畔。我一直安慰自己,他們只是感情好吠冤,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,289評(píng)論 6 390
  • 文/花漫 我一把揭開(kāi)白布浑彰。 她就那樣靜靜地躺著,像睡著了一般拯辙。 火紅的嫁衣襯著肌膚如雪郭变。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,231評(píng)論 1 299
  • 那天涯保,我揣著相機(jī)與錄音诉濒,去河邊找鬼。 笑死夕春,一個(gè)胖子當(dāng)著我的面吹牛未荒,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播及志,決...
    沈念sama閱讀 40,116評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼片排,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了速侈?” 一聲冷哼從身側(cè)響起率寡,我...
    開(kāi)封第一講書(shū)人閱讀 38,945評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎倚搬,沒(méi)想到半個(gè)月后冶共,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,367評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡潭枣,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,581評(píng)論 2 333
  • 正文 我和宋清朗相戀三年比默,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片盆犁。...
    茶點(diǎn)故事閱讀 39,754評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡命咐,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出谐岁,到底是詐尸還是另有隱情醋奠,我是刑警寧澤榛臼,帶...
    沈念sama閱讀 35,458評(píng)論 5 344
  • 正文 年R本政府宣布,位于F島的核電站窜司,受9級(jí)特大地震影響沛善,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜塞祈,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,068評(píng)論 3 327
  • 文/蒙蒙 一金刁、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧议薪,春花似錦尤蛮、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,692評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至哼御,卻和暖如春坯临,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背恋昼。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,842評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工看靠, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人焰雕。 一個(gè)月前我還...
    沈念sama閱讀 47,797評(píng)論 2 369
  • 正文 我出身青樓衷笋,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親矩屁。 傳聞我的和親對(duì)象是個(gè)殘疾皇子辟宗,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,654評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容

  • 類 方法 代碼塊 類宏 Eval方法 實(shí)例變量、方法吝秕、類 實(shí)例變量(Instance Variables)是當(dāng)你使...
    youngiii閱讀 1,111評(píng)論 0 51
  • 轉(zhuǎn)至元數(shù)據(jù)結(jié)尾創(chuàng)建: 董瀟偉泊脐,最新修改于: 十二月 23, 2016 轉(zhuǎn)至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 1,709評(píng)論 0 9
  • 01 Ruby元編程介紹和使用場(chǎng)景02 Ruby的類結(jié)構(gòu)03 Singleton Method單例方法以及supe...
    Jayzen閱讀 919評(píng)論 0 4
  • 什么時(shí)候需要讀這本書(shū)? 掃過(guò)一遍基本的 Ruby 語(yǔ)法烁峭,自己也寫過(guò)一些 Ruby 代碼容客,覺(jué)得 Ruby 也就是一個(gè)...
    Forelax閱讀 527評(píng)論 0 4
  • 這兩天,有三件小事觸動(dòng)了我约郁,內(nèi)心地震動(dòng)雖然沒(méi)有波瀾壯闊缩挑,卻也讓我在夜深人靜時(shí)想起他們!今天我只敘說(shuō)第一件事鬓梅,另外兩...
    ld熊壯壯閱讀 300評(píng)論 0 0