オブ指2022：オブジェクト指向からオブジェクト思考へ

筆者は仕事柄C#を使っている。そのためオブジェクト指向（以下**オブ指**）に則ってコードを書いているわけだが、そもそもオブ指とはどういうものなのだろうか、ということを考えずにはいられない。そこで、~~オブ指という言葉をを流行らせるため~~自分なりに勉強したこと、今オブ指を使うためにどうすればいいのかを考察してみた。

## そもそもオブ指とはどんなものか

今我々が知るところのオブ指を考えるうえで欠かせないのがクラスとインスタンスの関係である。一般にクラスはいくつかのフィールド（インスタンス変数）とメソッド（関数）を並べたものとして考えられる（フィールドとメソッドをまとめてメンバーと呼ぶ）。そしてインスタンスはクラスをオブジェクト化したものと言える。さらに、インスタンスとは関わりないクラス固有のメンバー（static、静的○○と呼ばれる）も存在している。ここで、クラスの（動的）メソッドはインスタンスごとの動作であるということが大事になる。つまり**インスタンスの状態を参照したり、更新したりするのが目的**ということだ。逆に静的メソッドはインスタンスの配列を処理するなど、**一般的な処理を定義する**（当然無関係なメソッドを定義したりもできるが、お行儀が悪すぎる）。このように**型と動作をパッケージングできる**のがCの構造体（struct）[^1]との違いだ。

そのため、我々は**オブジェクトに「考えさせる」形でプログラミングする**。そして戻り値としてその結果を返してもらう（メッセージング）わけだ。外側からオブジェクトの状態を変えることは基本的にしてはならず、そのオブジェクトに状態変更（もしくはこちらの指示に合った新規インスタンスを返すこと）をお願いするというのがスタンスだ。戻り値にクラスを使うことで、関連する結果をまとめて渡すこともできる。静的メンバーは（特にメソッド）は、むしろ従来型の手続き型プログラミングとの懸け橋として機能していると言える（特に分かりやすいのが手続きの集合体である`Main`）。そして、オブジェクトだと扱いづらい部分を補う役目もある。ただ、静的フィールドはグローバル変数みたいなもので扱いづらいと思われる。

オブ指には重要な側面が2つある。一つは外部に状態を晒さない「カプセル化」、もう一つは型を変えてもプログラムが動くようになる「ポリモーフィズム」だ。前者はコンストラクタでの初期化やアクセサの整備をすることで賄われ、後者は継承やインタフェイス、ジェネリクスにより担保されている。

## アクセサは必要なのか

よくオブ指のカプセル化とセットで語られるアクセサ（ゲッター・セッター）だが、特にセッターは槍玉にあげられている。外部から状態を変えられる勝手口（バックドア）だからだ。だったらフィールドの初期化をコンストラクタでやればいいし、組み込みみたいにメモリ容量の制限があったり、パフォーマンスを要求されない限り、値を書き換えるのではなく新しいインスタンスを作ればいいという考え方だ。それ以外でセッターを導入すべき唯一の理由は代入される値のフィルタリングだ[^2]。ゲッターはアダプター的に存在することが多いし、`getWild()`みたいなメソッドではなく別名として定義されることもままある。

セッター不要論は筆者も同感する。セッターはオブ指が捨てようとしている手続型プログラミング的風習だし、結果の予想をしやすくするためには無いに越したことは無い。ただし、フレームワークの都合でセッターを使わなければならないときもある（ASP.NET MVCのビューモデルなど）。それもフレームワークの流儀ということでやり過ごすしか無かろう。

C#ではアクセサの代替品としてプロパティがプログラム言語レベルで存在する。導入当初はフィールドを公開するための糖衣構文という感じだったが、`get`か`set`のいずれかにアクセス制限をかけられたり、コンストラクタでの初期化のみを許す`get`オンリープロパティなど、アクセサの用途をより直感的かつ安全に扱えるように改善されている。

## 継承よりも合成（委譲）とは？

多くのオブ指言語に備え付けられている機能が継承である。クラスAを拡張したクラスBを作るために使われる。要するにポリモーフィズムを実現するための機能だ。Javaを例に挙げるとこんな感じだ。

```java
public class RageAgainstTheMachine {
    public String playScratchWithGuitar(double length) {
        // 実装省略
    }
}

public class AudioSlave extends RageAgainstTheMachine {
    public String playTremolo(double rate) {
        // 実装省略
    }

    public static void main() {
        AudioSlave audioSlave = new AudioSlave();
        audioSlave.playScratchWithGuitar(4.5); // RageAgainstTheMachine.playScratchWithGuitarを読み出す
        audioSlave.playTremolo(0.5); // AudioSlave.playTremoloを読み出す
    }
}
```

とりあえずは良さそうだが、`AudioSlave`の動作が`RageAgainstTheMachine`に依存していることが問題になる。`RageAgainstTheMachine`の変更で「親亀こけたらみなこけた」事態になりかねないということは[20年ほど前の段階で指摘されている](https://qiita.com/NagayamaToshiaki/items/01260c8340fb9a6f8ef0)ことで、そこで出てきたのが継承よりも合成という概念だ。つまり、`RageAgainstTheMachine`を拡張して`AudioSlave`にするのではなく、両方で求められる機能を持った（インターフェイス）実装を用意する。

```java
public interface TomsRevolutionaryPlaying {
    public String playScratchWithGuitar(double length);
}

public interface GuitarPlayWithEffects {
    public String playTremolo(double rate);
}

public class TomMorello implements TomsRevolutionaryPlaying, GuitarPlayWithEffects {
    public String playScratchWithGuitar(double length) {
        // 実装省略
    }
    public String playTremolo(double rate) {
        // 実装省略
    }
}

public class RageAgainstTheMachine implements TomsRevolutionaryPlaying {
    private TomsRevolutionaryPlaying guitarist;
    public RangeAgainstTheMachine(TomsRevolutionaryPlaying t) {
        this.guitarist = t;
    }
    public String playScratchWithGuitar(double length) {
        return guitarist.playScratchWithGuitar(length);
    }
}

public class AudioSlave implements TomsRevolutionaryPlaying, GuitarPlayWithEffects {
    private TomsRevolutionaryPlaying leadGuitarist;
    private GuitarPlayWithEffects sideGuitarist; // 簡単のため、フィールドを分けることにする
    
    public RangeAgainstTheMachine(TomsRevolutionaryPlaying t, GuitarPlayWithEffects e) {
        this.leadGuitarist = t;
        this.sideGuitarist = e;
    }
    public String playScratchWithGuitar(double length) {
        return leadGuitarist.playScratchWithGuitar(length);
    }
    public String playTremolo(double rate) {
        return sideGuitarist.playTremolo(rate);
    }
}

public class Stage {
    public static void main() {
        TomMorello tom = new TomMorello();
        RageAgainstTheMachine ratm = new RageAgainstTheMachine(tom);
        ratm.playScratchWithGuitar(4.5); // tom.playScratchWithGuitar(4.5)が読みだされる
        TomMorello tom2 = new TomMorello();
        AudioSlave audioSlave = new AudioSlave(tom2, tom2);
        audioSlave.playScratchWithGuitar(4.5); // tom2.playScratchWithGuitar(4.5)が読みだされる
        audioSlave.playTremolo(0.1); // tom2.playTremolo(0.1)が読みだされる
    }
}

```

こうする実装のメリットは、`playScratchWithGuitar`の実装が固定されないので、`TomMorello`の最適化を気兼ねなく行える点だ。また、新しい`TomsRevolutionaryPlaying`を作ることもできる（現実には、トム・モレロさんは一人なんですけれどね）。

継承より合成というのは、バンドを考えると想像がしやすい（上のクラスの元ネタもバンドだし）。もし世界中のバンドがextendsで成り立つなら、メンバーをどんどん拡充することしかできず、最終的にはビッグバンドになってしまう。そして、ギターがいらないバンドもギタリストを持たなければならない（余計な実装や誤動作の温床）。対してimplementsならバンドは各パート（機能）を求め、ギタリストやベーシストがそれぞれの演奏を提供する。メンバーとそりが合わない事態になれば脱退して、バンドの残りはメンバーチェンジで要員を補充する（リファクタリング）。

よくよく考えれば、継承はオブジェクト指向言語の先駆者Simulaが取り入れたという**歴史的経緯**で受け継がれてきたのではなかろうか。もともとオブ指は研究室でのシミュレーションやOSの記述など、限定的な用途だったのが、C++やJavaのおかげで一斉に普及したという経緯がある。そのため、当初はうまく行っていてもユーザーベースが広がったために発覚した問題もあるだろう。確かにこれがあることで実装が楽になる部分は大きいが、本格的に企業で使われ出すようになってからは問題点が目立つようになったわけだ。

現代的なポリモーフィズム実装の方法としては、ジェネリクスもしくはテンプレートがある。

## まとめ

オブジェクト指向の大事な面は「カプセル化」と「ポリモーフィズム」だ。現代のオブ指を考える上では、**クラスはまとまりとして（適宜変換メソッドを通して）扱い、ポリモーフィズムはインターフェイスとジェネリクス（テンプレート）を組み合わせて実現する**。アクセサや継承は過去の遺産として「昔はそういうものを使ったんだよ」と考えるべきだろう。

[^1]: C++のstructはクラスの亜種であり、classとの唯一の違いはデフォルトのメンバー指定がpublicであることだ。要するにCの構造体からオブ指への移植を前提としているわけである。C#のstructは値型としてのclassという立ち位置で、主に比較的小規模なメンバーの集合体として定義される。有名なのがDateTimeだ。

[^2]: つまりint型のフィールドに1以上が入ることを確約するわけだ。0以下だったらエラーにするなり、1にするなりすればいい。