setterとgetterの実装


[JavaScript本格入門](https://www.amazon.co.jp/dp/477418411X/ref=cm_sw_r_tw_dp_U_x_wxAJCbT3GGKT9)(ISBN 978-4774184111)で基礎からJavaScriptを勉強するシリーズです。
今回はChapter5からアクセサーメソッドについてです。

アクセサーメソッドは、セッターメソッドとゲッターメソッドを合わせた言い方です。
アクセサーメソッドの実装の仕方と使用方法について勉強したのでまとめます。

## アクセサーメソッドを使わない実装

```js
let human = {
    name:"",
    age:"",
}

// 使う側（値のセット）
human.name = "Giorno Giovanna";
human.age = 15;

// 使う側（値のゲット）
console.log(human.name);  // Giorno Giovanna
console.log(human.age);   // 15
```

非常にシンプルな実装です。
続いてアクセサーメソッドを用いた実装をしてみます。

## アクセサーメソッドを使った実装

```js
let human = {
    set name(value){
        this._name = value;
    },
    get name(){
        return this._name;
    },
    set age(value){
        this._age = value;
    },
    get age(){
        return this._age;
    }
}

// 使う側（値のセット）
human.name = "Giorno Giovanna";
human.age = 15;

// 使う側（値のゲット）
console.log(human.name);  // Giorno Giovanna
console.log(human.age);   // 15
```

humanオブジェクトの中に[set構文](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Functions/set)と[get構文](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Functions/get)を用いてプロパティを定義してみました。

まずセッターメソッドの動きを見てみます。
1. `human.name`に値をセットすると、セッターメソッド`set name(value)`が呼ばれます
2. セッターメソッドの中では、`human._name`というプロパティを新しく作成し、そこに`value`の値を格納します

使う側は、`human.name`に値を代入したつもりなのですが、セッターメソッドの動きによって値を`human._name`という別のところに格納したことになります。

次にゲッターメソッドの動きを見てみます。
1. `human.name`にアクセスしようとすると、ゲッターメソッド`get name()`が呼ばれます
2. ゲッターメソッドの中では、`human._name`というプロパティの値を戻り値として返します

使う側は、`human.name`の値を取得したつもりなのですが、ゲッターメソッドの動きによって`human._name`という別の値を取得させられます。

セッターメソッドとゲッターメソッドの働きによって、間接的に`human._name`の値をやり取りするので、使う側からはhumanオブジェクトの中身がアクセサーメソッドを使って実装されてあろうがなかろうが、値のセットとゲットの方法は変わらないことに注目です。

値のセットとゲットにワンクッションあるだけですので、これではあまり何がうれしいのかわかりませんので、セッターとゲッターを使うと便利になる例を紹介します。

## 例1: プロパティに格納する値のチェックをしたい場合

`human.name`に入る値は文字列でないといけないし、`human.age`は数値であるべきです。
そのような場合、セッターメソッドにチェックする実装を入れてやることができます。

```js:before
let human = {
    name:"",
    age:"",
}

// 使う側（値のセット）
human.name = 15;                   // 望ましくない値として数値が入ってきた
human.age = "Giorno Giovanna";     // 望ましくない値として文字列が入ってきた

// 使う側（値のゲット）
console.log(human.name);  // 15
console.log(human.age);   // Giorno Giovanna
```
望ましくない値がそのままプロパティにセットされてしまいます。


```js:after
let human = {
    set name(value){
        if(typeof value === "string"){
            this._name = value;
        }else{
            console.log("nameが文字列じゃないじゃあないか！");
        }
    },
    get name(){
        return this._name;
    },
    set age(value){
        if(typeof value === "number"){
            this._age = value;
        }else{
            console.log("ageが数値じゃないじゃあないか！");
        }
    },
    get age(){
        return this._age;
    }
}

// 使う側（値のセット）
human.name = 15;
human.age = "Giorno Giovanna";

// 使う側（値のゲット）
console.log(human.name);  // undefined
console.log(human.age);   // undefined
```

```:実行結果
nameが文字列じゃないじゃあないか！
ageが数値じゃないじゃあないか！
undefined
undefined
```

セッターメソッドで値のチェックをすることができ、使う側が幾分か安全になりました。

## 例2: プロパティを読み取り専用にしたい場合

年齢は毎年変わりますが、名前はそう簡単には変わるものではありません。

なので、`human.age`は後から書き換え可能にしたいですが、`human.name`は後から変えられないように読み取り専用にしたいとします。

```js:before
let human = {
    name: "Giorno Giovanna",  // もともとの名前
    age: 15,                  // もともとの年齢
}

// 使う側（値のゲット）
console.log(human.name);  // Giorno Giovanna
console.log(human.age);   // 15

// 使う側（値のセット）
human.name = "汐華初流乃";    // 改名！
human.age = 16;              // 加齢！

// 使う側（値のゲット）
console.log(human.name);  // 汐華初流乃（改名されてしまった。望ましくない）
console.log(human.age);   // 加齢された（これは望ましい）
```
これでは`human.name`の値が後から書き換えられてしまいます。
アクセサーメソッドを使って`human.name`を読み取り専用にしてみます。

```js:after
let human = {
    _name : "Giorno Giovanna",  // もともとの名前
    _age : 15,                  // もともとの年齢
    get name(){
        return this._name;
    },
    set age(value){
        this._age = value;
    },
    get age(){
        return this._age;
    }
}

// 使う側（値のゲット）
console.log(human.name);  // Giorno Giovanna
console.log(human.age);   // 15

// 使う側（値のセット）
human.name = "汐華初流乃";    // 改名！
human.age = 16;              // 加齢！

// 使う側（値のゲット）
console.log(human.name);  // Giorno Giovanna（改名が阻止できた）
console.log(human.age);   // 加齢された（これは望ましい）
```

改名が阻止できました。
何をしたかというと、nameのセッターメソッドの実装をやめゲッターメソッドだけ持つようにするようにしました。これにより、`human.name`は読み取り専用となりました。
`human.age`は後で加齢できるように読み書きできるようにセッターメソッド、ゲッターメソッドの両方を持ちます。

## 例3: 例1と2を組み合わせる

プロパティに入れられる値をチェックし、なおかつ読み書きの制御も行います。
`human.name`は後から書き換えできないように、`human.age`はプロパティの値を加算するための専用の関数を定義します。これにより不用意に年齢を減らされたりすることがなくなるという意図です。

```js:sample3
let human = {
    _name : "Giorno Giovanna",  // もともとの名前
    _age : 15,                  // もともとの年齢
    get name(){
        return this._name;
    },
    get age(){
        return this._age;
    },
    addAge(){                 // 年齢を加算するための関数
        human._age++; 
    }
}

console.log(human.name);  // Giorno Giovanna
console.log(human.age);   // 15

// 使う側（値のセット）
human.name = "汐華初流乃";      // 改名！
human.addAge();                // 加齢！

// 使う側（値のゲット）
console.log(human.name);  // Giorno Giovanna (セッターメソッドを持たないので改名は失敗)
console.log(human.age);   // 16 (addAgeを使って加齢できている)
```


## まとめ

セッターメソッドとゲッターメソッドを使うことで、
1. プロパティに予期しない値が入るのを防ぐこと
2. プロパティの値が後から書き換わることを防ぐこと
ができることがわかりました。

ただし、この実装では限界があり、使う側が直接`human._name`や`human._age`をアクセスしてしまうと、1、2が保証できなくなってしまいます。

コーディング規約などでアンダースコア付きのプロパティには直接アクセスしないことをお約束しておく必要があります。

## おまけ

クロージャやシンボルを使うともう少し安全なアクセサーメソッドを実装することができます。

以下の記事で紹介されているものがわかりやすいです。
[JavaScriptのgetter/setterの使い方を考えよう](https://qiita.com/hogefuga/items/eefbbf6f4d2ff682c7e0)