Java
PHP
Kotlin
オブジェクト指向
Smalltalk

(オブジェクト指向における) getter/setter メソッドの使いどころ

「getter/setterメソッド、いちいち書くのだるいんだけど、どうして書かなきゃいけないの?結局どどういう所で書けばいいの?」っていう声をよく耳にするので、記事にしてみました。

(マサカリが飛んでくる事を100%保証)

尚、ここではフィールド、インスタンス変数、プロパティ、メンバ変数といったような似たような性質を持つ単語については使い分けをせず、総称してフィールドと呼ばせていただきます。
また、メソッド、メンバ関数などについても、総称してメソッドと呼ばせていただきます。

基本的には、

まずはgetter/setterメソッドという概念が生まれた経緯を簡単に説明します。

オブジェクト指向プログラムの一つの性質として、多態性というものがあります。これは例えば、

  • Smalltalkにおいては、レシーバがどんなオブジェクトであろうと、送るメッセージに応答できるかどうかだけを気にすればよい
  • Javaなら、特定のInterfaceを実装しているオブジェクトなら実装を気にせず、そのオブジェクトの特定のInterfaceのメソッドを呼び出せる

という性質です。

つまり、実装ではなく、インターフェイスに対してプログラミングをするということです。
これにより、ソフトウェア開発のコストを低減することができるようになります。

インターフェイスに対するプログラミング、つまり実装への依存を最大限取り除くプログラムを書く際、最も重要なのは具体的な実装部にアクセスできないようにする(カプセル化)事です。
フィールドはまさしく内部実装に含まれるので、これに外部から直接アクセスできないようにする為に、代わりとなるメソッドを用意してあげる必要があります。
これにより、「特定のフィールドに対し、そのデータを外部から取得するメソッド、あるいはそのデータを外部から変更するメソッドを用意してあげる」というパターンが生まれます。このパターンに該当するメソッドが後々、「getter/setterメソッド」と呼ばれることになったのです。

getter/setterメソッドの使用例をJavaで書くとこんな感じです

class User{
    private String name;

    public String getName(){
        return name;
    }

    public void setName(String name){
        this.name = name;
    }
}

これはよく見かけるやつですね。

ちなみに、カプセル化、getter/setterメソッドの概念を一番よく反映しているのはSmalltalkだと思います。
Smalltalkにおいてはインスタンス変数の(Java等で言う所の)アクセス修飾子はprotectedから変えられないようになっているからです。

Object subclass: #User
    instanceVariableNames: 'name'

User>>name
    ^ name

User>>name: aName
    name := aName

ここで、Smalltalk、あるいはその系列のOOPLのsetter/getter メソッドはよくあるスタイルとは異なり、上の例みたいにname, name: の形式になっています
(実に美しいですね…)

でも、例外(?)もある

getter/setterメソッドはコードが冗長になってしまう、という欠点もあります。
下の例ですと、たった三つフィールドを定義するだけでもこれだけコードが膨れ上がってしまいます。

class User{
    private String name;
    private int age;
    private boolean isAdmin;

    public String getName(){
        return name;
    }

    public void setName(String name){
        this.name = name;
    }

    public int getAge(){
        return age;
    }

    public void setAge(int age){
        this.age = age;
    }

    public boolean getIsAdmin(){
        return isAdmin;
    }

    public void setIsAdmin(boolean isAdmin){
        this.isAdmin = isAdmin;
    }
}

ここでは、getter/setterメソッドを定義しない方が良いケースについて幾つか説明します。

PIを用いる場合

PI(Persistence Ignorance)は、「ただの構造体として振る舞う」クラスのパターンです。POJO(Plain old java object)とも呼ばれます。
このパターンを用いると、上記のJavaコードは次のように書き換えられます。

class User{
    public String name;
    public int age;
    public boolean isAdmin;
}

単純な構造体では、フィールドそのものがインターフェイスとして振る舞うので、冗長なsetter/getterメソッドを記述する必要はなく、オブジェクト指向プログラム的にもこのように書いても問題ない、ということです。

このパターンはよくモデルクラスなどで使われます。

ActiveRecord、Eloquent ORM等を用いる場合

Ruby on RailsやLaravel等のActiveRecordでは、コード上では定義されていないフィールドに動的にアクセスすることができます。

例えばLaravelでは、

class User extends Model{

}

$user = User::first();
$user->id; // 1とか
$user->name; //ogiwaraとか

といったようにアクセスでき、当然こういった場合はgetter/setterメソッドをいちいち定義する必要はありません。

ちなみに、コード上では定義されていなくても、PHPなら@propertyなどをPHPDoc
に書いておけばIDEなどでヒンティングの支援が受けられるので下のように書いておきましょう。

/**
 * @property int $id
 * @property string $name
 */
class User extends Model{

}

言語仕様に組み込まれてるの

Kotlin, C#等には、getter/setterメソッドをより簡単に書けるようにする言語仕様があります。
Kotlinにおいて、上でJavaで作ったUserモデルと同じものを作ることを考えます。

class User{
    public name: String
    public age: Int
    public isAdmin: Boolean
}

ここで、「nameフィールドにアクセスした時に、ログを残すようにする」というように仕様変更する際は、次のように書き換えればいいだけです。

class User{
    public name: String
        get(){
            println("nameにアクセスされました")
            name
        }
    public age: Int
    public isAdmin: Boolean
}

つまり、「フィールドの取得、変更に対し、後から処理を拡張できるようになっている」ということです。
この機能は以下のように、フィールドの委譲をする役割も果たすことができます。

class User{
    private realName: String
    public name: String
        get(){
            realName
        }
        set(value){
            realName = value
        }
}

この機能は、Kotlin,C#においては「getter/setter」という名前で提供されています。

まとめ

Kotlin, C#とかみたいに、あとから拡張できるようになっている言語仕様が一番うれしいよね