Java
オブジェクト指向

結局のところgetter/setterは要るのか?要らないのか?

More than 1 year has passed since last update.

Javaを勉強するに当たって表題通りの疑問が湧いたので、色々と調べました。
getter/setterが必要とされる理由に関しては各所で散々解説されているので、"不要派"の意見を重点的にまとめています。

まずオブジェクト指向プログラミングの定石

  • フィールド=private、メソッド=public
  • フィールドを操作するためにgetterメソッドとsetterメソッドを用意する

= カプセル化である。

カプセル化が好まれる理由

  • メンバ変数のアクセス制御ができるようになる
  • クラス内部のデータ表現を変えた場合でも呼び出し側のコードを変更する必要がない
  • メンバ変数の参照や変更を追跡することができる
  • 値の正当性チェックを入れることができる
  • オブジェクト指向とはそういうものである。

オブジェクト指向プログラムでgetter/setterメソッドを使わなければならない10の理由

プロパティについて

C#やRuby、SwiftなどJava以外のオブジェクト指向言語にはgetter/setterを実現するプロパティというしくみが備わってる。(Rubyはちょっと違うけど)
呼び出す側からはあたかもフィールドを直接いじってるように見えるが、裏ではgetter/setterのようなメソッドを介してフィールドを変更している。そのメソッドをいちいち自分で細かく定義しなくてもいいのがプロパティの特徴。

※Java7ではプロパティ構文が導入されかけたけど、結局導入は見送られた。

例としてC#

基本は以下のような形

sample1.cs
public class Date {
    private int month;

    // Monthプロパティ
    public int Month { 
        get { return month; }
        set {
            if ((value > 0) && (value < 13)) { month = value; }
        }
    }
}

class Program {
    static void Main() {
        Date date = new Date();
        date.Month = 12;
        Console.WriteLine("今は" + date.Month + "月です");
    }
}

純粋にget/setするだけならもっと簡単に書くことができる。

sample2.cs
public class Date {
    // プログラマが参照できないprivateフィールドが自動で生成される
    public int Month { get; set; }
}

getだけ宣言すれば読み取り専用なんかにもできる。

例としてRuby

Rubyではインスタンス変数にアクセスするためのアクセスメソッドというものが用意されている。

sample.rb
class Date
    def initialize(month = 7)
        @month = month
    end

    attr_accessor :month # アクセスメソッド
end

date = Date.new()
date.month = 12
puts "今は" + date.month + "月です"

対象となる変数名に対してattr_reader attr_writer attr_accessorのいずれかを宣言することでgetter/setterを個別に定義する代わりとなる。
atter_readerを宣言すれば読み取り専用、attr_writerを宣言すれば書き込み専用にできる。

※アクセスメソッドは単純に値の参照と更新のみを行う。setter内で個別に処理(値検証など)を行う場合は自分でsetterを定義する必要がある…たぶん。
※アクセスメソッドを丸ごと再定義する方法はある。 >Ruby: attr_accessor を拡張する

publicフィールドを利用する例外

定数を利用する場合

publicなフィールドを利用する例の1つとして「定数の利用」が挙げられる。
通常、Javaでの定数はstatic finalで宣言される。finalで宣言されているためそもそも値の変更が効かないので、privateにする必要もない。
staticが付いてるのはインスタンスが無くても利用できるようにするため。)

publicフィールドは、たとえばstatic finalフィールドのように「変更できない定数値」等にのみ使用し、通常のフィールドはprivateフィールドにしてクラスの内部で管理するべきである。
publicフィールドとは : JavaA2Z

特定のフレームワークを利用する場合

Seasar2というJavaのフレームワークにおいては、publicフィールド=プロパティとして扱う仕組みになっている。

Seasar2はpublicフィールドをプロパティとみなすことができるので、 getter,setterは記述する必要はありません。 publicフィールドは、ELやStrutsからでも認識できるようになっています。 そのからくりに興味のある方は、org.seasar.struts.actionのソースを眺めてください。
Super Agile Struts - Tutorial

「getter/setterを使わない」論

多くの参考書などで「getter/setterによるカプセル化はオブジェクト指向の大原則」とされる一方、「getter/setterを使うべきではない」と主張する人たちが少なからずいる。
ただしほとんどの主張においてフィールドはprivateにしてメソッド経由の操作を行うという原則は変わらない。

getter/setterを使いたくない理由

1. それぞれのメソッドの管理が煩雑になる

すべてのフィールドに対してgetter/setterを定義すると、当たり前だけどコードがどんどん長くなる。
しかも似たような関数ばかりでわかりづらく、メンテナンスが非常に面倒。
これらのデメリットは、プロパティが言語仕様として定められていないJavaでは特に顕著になる

ただIDEを使えばgetter/setterは自動生成してくれるし、メソッドの管理もある程度楽になる。

2. 純粋なgetter/setterはカプセル化を実現しない

本来カプセル化はフィールドへのアクセスを制限するためのものだが、getter/setter(特に、内部で一切の加工を行わないもの)を使ってフィールドへアクセスできてしまうと意味がない。

getter/setterメソッドを置き換える

他クラスのフィールド値にアクセスしようとしたとき、「getter/setter以外にもっといいメソッドがあるんじゃないの?」という考え方。

1. Tell, Don't Ask.

「Tell, Don't Ask.」とは、オブジェクト指向プログラミングにおいて"良い"とされる考え方のひとつ。
日本語だと大体「求めるな、命じよ」と訳されることが多い。
もうちょっと具体的にすると、ある処理をする際、その処理に必要な情報をオブジェクトから引き出さないで、情報を持ったオブジェクトにその処理をさせろということ。

getterというのはまさにオブジェクトから情報を引き出すメソッドである。
つまり、あるクラスで他クラスのgetterを呼び出すような処理を実装している場合、その処理は本来呼び出されるクラス側で実装されるべきだということ。
setterも同様に、フィールドの中身を変えるような処理はそもそもそのフィールドをもつクラス内で完結させるべきである、という考え方。

具体例など以下の記事を読むとわかりやすいかと思います。

オブジェクト指向設計 getter, setterを使うなとはどういうことか - Qiita
Getter, Setter の存在意義 | とわいらいと・せれなーで

2. オブジェクトは生き物である

まずは以下の記事を読んでいただきたい。

Getters/Setters. Evil. Period.(原文)
Getter/Setterは悪だ。以上。 | To Be Decided(日本語訳)

記事では「犬」というクラスに「ボール」を持たせる場合を例に挙げている。
「犬にボールをセットする」よりは「犬がボールを受け取る」のほうが自然(むしろそうするべき)でしょ?ということらしい。

つまり「オブジェクトは生き物である」というのは、オブジェクトを「受動的にデータをget/setされるもの」ではなく「能動的にデータを受け取ったり返したりするもの」とする概念である。この考え方でいくと、get○○やset○○というメソッド名は言葉の意味が通じないので、もっとそれっぽいメソッド名をつけましょう、となる。メソッド名が変わったところで処理内容が変わらないとしても。

まとめ

  • オブジェクト指向では原則フィールドはprivateに保つべし。
  • フィールドを操作するための最小限のメソッドがgetter/setter(プロパティ)である。
  • getter/setterを使うときは、一度立ち止まって別のメソッドに置き換えられないかを考えてみよう。

getter/setterはカプセル化を実現するためだけに作られた機能であって、実際のプログラムを意識したものではない。どんな値をどんな目的のためにget/setする必要があるのかを意識すると、getter/setterというメソッドには確かに違和感を覚える。

オブジェクト指向 の闇は深い は奥が深いですね。

参考

getter/setterとはなんだったのか - プログラマーの脳みそ
getter/setterが必要とされてきた理由、必要ない論が浮上した理由、Javaでプロパティ機能が実装されない経緯など総合的にわかりやすくまとめてあります。これだけ読んどけば良い感。