# attr_accessor 是幹嘛的？

> 

Published: 2015-03-21
URL: http://cdn.kaochenlong.com/attr_accessor

---

先說結論：

&gt; attr_accessor 會幫你在 Ruby 的類別裡產生一對 getter 以及 setter 方法。

不過我想這結論對新手來說有講跟沒講一樣，讓我們繼續往下看。

&lt;!--more--&gt;

跟別的程式語言比起來，Ruby 可以省略很多東西，像是呼叫方法的時候可以不用小括號，回傳資料的時候有時候不用特別加 `return`。

我們先來看一段範例：

```ruby
class Girl
  def initialize(age)
    @age = age
  end
end

mary = Girl.new(20)
```

這是一個簡單的 Ruby 類別，我用 `Girl` 類別建一個名為 `mary` 的實體（instance），並且在初始化的時候就設定她的年紀為 20。`Girl` 類別裡有一個 `@age` 這個實體變數（instance variable），也許你會猜說如果要知道 `mary` 的年紀的話，只要：

```ruby
puts mary.age
```

就行了，但一執行就會發現錯誤訊息：

    undefined method `age&#39; for #&lt;Girl:0x007f93a609fa10 @age=20&gt; (NoMethodError)

怪了，我是想要存取 `age` 這個屬性，為什麼錯誤訊息卻是 `undefined method`?

在解釋之前，要先說明幾個 Ruby 這個程式語言跟別家程式語言在設計上的不同：

### 一、Ruby 的方法呼叫，常常會適時的省略小括號：

舉個例子來說：

```ruby
def say_hi_to(name)
  puts &quot;hi, #{name}&quot;
end

say_hi_to(&quot;eddie&quot;)
```

但我們通常會寫成：

```ruby
say_hi_to &quot;eddie&quot;
```

在呼叫方法的時候省略小括號，這在 Ruby 是很常見的寫法。

### 二、Ruby 並沒有「屬性」（property/attribute）這樣的東西：

雖然 `mary` 看起來有一個 `@age` 實體變數，但不表示是可以直接存取的屬性。硬是要用 `mary.age` 問她年紀，或是要用 `mary.age = 18` 來設定她的年紀，她都會賞你一巴掌，給你錯誤訊息的。

`mary.age` 你以為是讀取 mary 上的 age 屬性，但事實上是在執行 `mary.age()` 方法，只是小括號被省略了。`mary.age=18` 你以為是設定 mary 的 age 屬性，但事實上是執行 `mary.age=(18)` 方法，只是小括號被省略了。

在 Ruby 裡，很多東西都跟你看到的不太一樣，例如，你以為 `1 + 2` 是簡單的數學運算嗎? 其實它是 `1.+(2)`，它是對數字物件 1 送了一個 `+` 的訊息，並且把數字物件 2 當做參數傳給它。

好啦，既然知道它們都是方法，那要怎麼定義它們呢?

```ruby
class Girl
  def initialize(age)
    @age = age
  end

  def age
    return @age  # 這個 return 通常會省略
  end

  def age=(new_age)
    @age = new_age
  end
end
```

上面這段範例中，第 6 ~ 8 行的方法會回傳 `@age`，又常稱之 getter；第 10 ~ 12 行的方法它會設定 `@age` 的值，故又常稱之 setter。

不過...等一下! 為什麼方法後面有個等號?

請把等號當做一般的字母看待。在 Ruby 定義方法的時候，等號跟其它字元一樣都可以是方法名字的一部份，只是這個特殊字元必須要放在方法名稱的結尾(其實包括問號跟驚嘆號也都一樣)。

這個方法就叫做 `age=`，要使用它就是用 `age=(18)`，沒錯，就是連等號一起呼叫它。所以，其實標準形態應該長這樣：

```ruby
mary.age=(18)
```

又，因為前面提到，Ruby 可以省略小括號，所以可寫成：

```ruby
mary.age=18
```

然後，Ruby 又有幫忙加了一些語法糖衣，讓你在中間加一些空白字元也是可以的：

```ruby
mary.age = 18
```

最後就會讓你看起來像是在設定 age 屬性了。

## 這...會不會太麻煩了點?

照這樣說，如果每次想要用類似的屬性寫法，就必須要寫一對方法來回傳、設定，會不會有點太麻煩啊。

是的，就是要這麼麻煩。不過請放心，工程師都很懶的，所以有另外設計了幾個方法可以讓你快速的產生前面提到的 getter/setter。

如果你的 getter/setter 很單純，就只是有回傳、設定實體變數的話，那你可用 Ruby 內建的幾個方法：`attr_reader`、`attr_writer` 以及 `attr_accessor`：

```ruby
class Girl
  attr_accessor :age
  def initialize(age)
    @age = age
  end
end

mary = Girl.new(20)
puts mary.age    #=&gt; 20
mary.age = 18
puts mary.age    #=&gt; 18
```

其中，`attr_reader` 只會幫你產生 getter，`attr_writer` 只會幫你產生 setter，而 `attr_accessor` 則會幫你產生 getter 及 setter。如果不相信，可以執行下面這行看一下：

```ruby
p Girl.instance_methods(false)
```

應該會看到以下結果：

```ruby
[:age, :age=]
```

的確是產生了兩個方法，分別是 `age` 以及 `age=`。

### 用了 attr_accessor 還能自己寫 getter 或 setter 嗎?

當然是可以的，例如：

```ruby
class Girl
  attr_accessor :age

  def age=(new_age)
    @age = (new_age &gt; 18) ? 18 : new_age  # 如果大於 18，就只設定 18..
  end
end

mary = Girl.new
mary.age = 30     # 即使設定為 30 歲...
puts mary.age     # 還是會永保 18 歲 :)
```

因為你重新定義了 `age=` 方法，在執行的時候 Ruby 會跳出來跟你碎碎唸說 `warning: method redefined; discarding old age=`，但程式還是可執行。

所以，如果你只是想要客制化自己的 getter 或 setter 的話，可將 `attr_accessor` 改為 `attr_reader` 或 `attr_writer`，就不會有這個警告訊息了。

以上，希望對大家有幫助 :)

