15分でわかる かんたんオブジェクト指向

最近、オブジェクト指向プログラミング[^1]について説明する機会が多く、これからもっと多くなりそうなので、文章にまとめることにしました。

オブジェクト指向とは何かという説明はたくさんありますが、 **何のためにオブジェクト指向プログラミングがあり、どのように使うのか** を、具体例を交えて説明した文章がなかなか見つからなかったので自分で書いてみました。個人差はあると思いますが、目安として **15分程度で読了** [^2]できる分量を想定しました。

本投稿は継続的にアップデートして改良していきたいと考えています。わかりづらい点やおかしな点などあれば、コメントか <a href="https://twitter.com/koher" target="_blank">&#x0040;koher</a> までツイートいただけるとうれしいです。

## 対象

- オブジェクト指向の考え方がよくわからない人
- プログラミングの初歩（条件分岐、ループ、配列、関数など）はわかっている人
- 作りたいものを自分で考えてある程度作れる人

## 一人のデータ

プログラムで会員や顧客、ユーザーなど人のデータを扱うことはよくあります。そのようなケースを例に考えてみましょう。

話を簡単にするために、「人」は次の三つのデータだけを持っていると考えます。

- 姓 (familyName)
- 名 (givenName)
- 年齢 (age)

プログラムで書くと次のような感じでしょうか[^3]。`String` は文字列、 `Integer` は整数を表す型、 `println` は文字列を表示して改行する関数だとします。

```java:(A)
// 変数を宣言して値を代入
String givenName = "Albert";    // 名
String familyName = "Einstein"; // 姓
Integer age = 26;               // 年齢

println(givenName + " " + familyName); // フルネームを表示
```

実行結果は次のとおりです。

```
Albert Einstein
```

## 複数人のデータ

**(A)** では一人だけでしたが、複数人のデータを扱うには配列にします。

```java:(B)
String[] givenNames = ["Albert", "Isaac", "Galileo"];
String[] familyNames = ["Einstein", "Newton", "Galilei"];
Integer[] ages = [26, 43, 46];

// 全員のフルネームを表示
for (Integer i = 0; i < 3; i++) {
    println(givenNames[i] + " " + familyNames[i]);
}
```

実行結果は次の通りです。

```
Albert Einstein
Isaac Newton
Galileo Galilei
```

## オブジェクトにまとめる（クラスとフィールド）

**(B)** のコードは少し読みづらいです。 `givenName`, `familyName`, `age` は人に属しているのに、それらがばらばらの配列に格納されているからです。そこで、三つの値を一つの **オブジェクト** にまとめたいという気持ちになります。

```java:(C)
class Person { // 人を表す型を宣言
    String givenName;
    String familyName;
    Integer age;
}

// Person型の変数personを宣言し
// givenName, familyName, ageの値を設定
Person person = {
    givenName: "Albert",
    familyName: "Einstein",
    age: 26
};

// フルネームを表示
println(person.givenName + " " + person.familyName);

// 年齢を1増やす
person.age = person.age + 1;
```

このコードでは、 `givenName`, `familyName` , `age` を持った `Person` （人）という新しい型を宣言し、そのオブジェクトに `"Albert"`, `"Einstein"`, `26` という値を持たせています。

`Person` を宣言するときに `class` というキーワードを使っていますが、オブジェクト指向では `Person` のような型を規定するものを **_クラス_** と呼びます。

また、`givenName`, `familyName`, `age` のことを `Person` クラスの **_フィールド_** と呼びます。フィールドはオブジェクトにひも付いた変数のようなもので、 **_メンバ変数_** と呼ばれることもあります。

## メソッド

先程から何度もフルネームを表示していますが、よく書く処理は関数にすると便利です。

```java:(D)
// フルネームを返す関数
String getFullName(Person person) {
    return person.givenName + " " + person.familyName;
}

// フルネームを表示
println(getFullName(person));
```

これでフルネームを表示するのが楽になりました。しかし、 `person.givenName` や `person.familyName` に対して、 `getFullName(person)` というのは統一感がありません。同じ `person.` を使って、 `person.getFullName()` と書けるとより良いように感じられます。たとえば、次のような感じです。

```java:(E)
class Person {
    String givenName;
    String familyName;
    Integer age;
    
    // このオブジェクトのフルネームを返す
    String getFullName() {
        return this.givenName + " " + this.familyName;
    }
}

// フルネームを表示
println(person.getFullName());
```

フィールドがクラスに紐付いた変数なら、この `getFullName` はクラスに紐付いた関数です。そのような関数を **_メソッド_** （ **_メンバ関数_** ）と呼びます。

`getFullName` は内部で `givenName` や `familyName` にアクセスできないといけません。 **(D)** では `getFullName(person)` のように `person` が引数として渡されるので、 `person.givenName` のようにしてフィールドにアクセスできます。

**(E)** の場合は `person.getFullName()` のように、 `person` は引数として渡されません。その代わりに `this` を介して `givenName` や `familyName` にアクセスしています。この `this` とは何でしょうか。 `this` は引数として明示的に書かれていませんが、 `person.getFullName()` のようにしてメソッドを呼び出した場合、隠れた引数 `this` があって、暗黙的に `this` に `person` が渡されると考えて下さい。

## コンストラクタ

クラスにはフィールドやメソッドと並ぶ重要な構成要素があります。それが **_コンストラクタ_** です（ **_イニシャライザ_** と呼ばれることもあります）。

コンストラクタはオブジェクトが生成されるときに呼ばれるメソッドのようなものです。コンストラクタでフィールドを初期化することで、フィールドが未初期化なオブジェクトが生成されるのを防げます。

```java:(F)
class Person {
    String givenName;
    String familyName;
    Integer age;
    
    // コンストラクタの実装
    Person(String givenName, String familyName, Integer age) {
        // 引数で与えられた値でフィールドを初期化
        this.givenName = givenName;
        this.familyName = familyName;
        this.age = age;
    }
    
    ...
}

// コンストラクタでオブジェクトを生成と同時に初期化
// （コンストラクタは「new クラス名(...)」で呼び出す言語が多い）
Person person = new Person("Albert", "Einstein", 26);
```

## カプセル化

これまでは `person.familyName` のように、フィールドに直接アクセスしてきました。しかし、それはオブジェクト指向において望ましくないこととされています。

それでは、フィールドに格納された値にアクセスするにはどうすればよいのでしょうか。答えは↓です。

```java:(G)
class Person {
    String givenName;
    String familyName;
    Integer age;

    ... // コンストラクタとgetFullNameメソッドを省略
    
    // givenNameを返すだけのメソッド
    String getGivenName() {
        return this.givenName;
    }
    
    // givenNameを変更するだけのメソッド
    void setGivenName(String givenName) {
        this.givenName = givenName;
    }
    
    // familyNameを返すだけのメソッド
    String getFamilyName() {
        return this.familyName;
    }
    
    // familyNameを変更するだけのメソッド
    void setFamilyName(String familyName) {
        this.familyName = familyName;
    }
    
    // ageを返すだけのメソッド
    Integer getAge() {
        return this.age;
    }
    
    // ageを変更するだけのメソッド
    void setAge(Integer age) {
        this.age = age;
    }
}

// 姓と年齢を表示
println(person.getFamilyName() + " (" + person.getAge() + ")");

// 年齢を1増やす
person.setAge(person.getAge() + 1);
```

クラスの外部からは `person.getFamilyName()` のようにメソッドを介してフィールド値にアクセスするようにします。しかし、こんなことをして何がうれしいのでしょうか。ただコードが長くなり、冗長になっただけではないでしょうか。

次のような例を考えてみます。

姓や名を単独で表示することに比べてフルネームを表示する方がずっと多いシステムを考えます。毎回フルネームを生成する処理がボトルネックになっているとしましょう。

そうすると、 `Person` が `givenName` と `familyName` をばらばらに持ち、 `getFullName` が呼び出されたときに毎回フルネームを生成するよりも、 `Person` の内部ではフルネームを保持しておいて、 必要に応じて名や姓を生成する方が効率的です。 **(G)** を書き換えると次のようになります[^4]。

```java:(H)
class Person {
    // givenNameとfamilyNameをばらばらに持つ代わりにfullNameで持つ
    String fullName;
    Integer age;
    
    ... // コンストラクタを省略
    
    String getFullName() {
        // 単にfullNameを返すだけで良い
        return this.fullName;
    }
    
    String getGivenName() {
        // fullNameのスペースより前の部分を返す
        // （splitはStringを分割してString[]を返すStringクラスのメソッドとする）
        return this.fullName.split(" ")[0];
    }
    
    void setGivenName(String givenName) {
        // 与えられたgivenNameを使ってfullNameを更新
        this.fullName = givenName + " " + getFamilyName();
    }
    
    String getFamilyName() {
        // fullNameのスペースより後の部分を返す
        return this.fullName.split(" ")[1];
    }
    
    void setFamilyName(String familyName) {
        // 与えられたfamilyNameを使ってfullNameを更新
        this.fullName = getGivenName() + " " + familyName;
    }
    
    ... // getAge, setAgeを省略
}

// 姓と年齢を表示
println(person.getFamilyName() + "(" + person.getAge() + ")");

// 年齢を1増やす
person.setAge(person.getAge() + 1);
```

**(G)** と **(H)** では `Person` の実装が全く異なりますが、最後の姓と年齢を表示するコード（↓）は同じです。

```java
// 姓と年齢を表示
println(person.getFamilyName() + "(" + person.getAge() + ")");
```

`Person` の実装が異なるにも関わらず、 `Person` を使うコード（↑）に変化がないのはなぜでしょうか。それは、 `Persopn` の実装と振る舞いが分離されているからです。

`Person` がどのようなフィールドを持っているかや、メソッドをどのように実装するかというのは「実装」に当たります。一方で、どんなメソッドを持っていてどんな引数を渡すとどのような戻り値が返されるかというのは `Person` の「振る舞い」です。「振る舞い」とは、そのクラスに何ができるのかということです。

**(G)** と **(H)** では `Person` の「実装」が大幅に変更されていますが、「振る舞い」は一切変更されていません。このように、「実装」を「振る舞い」から分離し、隠蔽することを **_カプセル化_** と呼びます。

カプセル化されたクラスを使う人にとっては、目に見えるのは「振る舞い」だけです。クラスを使う人は、そのクラスを使って何ができるのかに興味があり、それがどのように実装されているのかについては興味がありません。

これは、関数の考え方と似ています。関数を使う人が知りたいのはその関数で何ができるのか、その関数をどのように使うのかということであって、関数の実装の詳細を知りたいとは思いません。 `println` を日頃から使っていても、 `println` の中身を知っている人は少ないでしょう。

それと同じように、データ（フィールド）と関数（メソッド）がセットになったクラスにおいても、「実装」と「振る舞い」を分離して「実装」を隠蔽したのがカプセル化です。カプセル化されたクラスを利用する人は、「実装」について気にする必要がありません。また、「実装」が変更されても「振る舞い」が変わらなければ、そのクラスを利用している箇所を変更する必要はありません。

もし `Person` がカプセル化されておらず、「実装」と「振る舞い」が分離されていない場合、上記のようにパフォーマンス向上のために `Person` に `fullName` を持たせようとすると大変です。 `person.familyName` となっていた箇所を、すべて `person.fullName.split(" ")[1]` に書き換えなければなりません。カプセル化によって「実装」と「振る舞い」を分離することで、「実装」の変更による影響をカプセルの中に閉じ込めることができ、コードの修正が容易になります。

## アクセサ

↑で見た `getXxx` のようなメソッドのことを **_getter_** 、 `setXxx` のようなメソッドのことを **_setter_** と呼びます。また、getterとsetterをあわせて **_アクセサ_** や **_アクセサメソッド_** と呼びます。

アクセサはカプセル化を助けるだけでなく、フィールドにはできないことを可能にします。

たとえば、 `setAge` メソッドは年齢をセットしますが、負の数を渡されると困ります。負の数を渡された場合のエラー処理方法は色々考えられますが、ここでは0に丸めて値を格納したいとしましょう。

そのためには `setAge` メソッドを次のように実装します。

```java:(I)
class Person {
    ...
    void setAge(Integer age) {
        if (age < 0) {
            this.age = 0;
        } else {
            this.age = age;
        }
    }
    ...
}
```

このように、アクセサを介してフィールドにアクセスするなら、単に代入したり参照したりするだけでなく付加的な処理を加えることができます。

## 継承

ここまで `getFullName` は西洋式に『名 姓』の順に文字列を結合していました。しかし、日本人のように『姓 名』の順に表示したいこともあります。そこで、フルネームが『名 姓』の順になる `WesternPerson` （西洋人）クラスと、『姓 名』の順になる `EasternPerson` （東洋人）クラスを考えてみます。

`WesternPerson` と `EasternPerson` を別々に実装することもできますが、二つのクラスは `getFullName` 以外はまったく同じです。同一の処理を複数箇所に書くのは無駄ですし、片方にバグがあればもう片方も修正しなければならないのでメンテナンス性も下がります。何とか共通部分を一つのコードで書くことはできないでしょうか。

それを実現するのが **_継承_** です。クラスは別のクラスを継承することで、そのクラスの持つ機能を自分自身に取り込むことができます。

`WesternPerson` と `EasternPerson` の共通機能を `Person` として実装し、それを継承して `getFullName` だけを別々に実装するようにします。

```java:(J)
// getFullName以外をPersonに実装
class Person {
    String givenName;
    String familyName;
    Integer age;

    Person(String givenName, String familyName, Integer age) {
        this.givenName = givenName;
        this.familyName = familyName;
        this.age = age;
    }
    
    String getGivenName() {
        return this.givenName;
    }
    
    void setGivenName(String givenName) {
        this.givenName = givenName;
    }

    String getFamilyName() {
        return this.familyName;
    }
    
    void setFamilyName(String familyName) {
        this.familyName = familyName;
    }
    
    Integer getAge() {
        return this.age;
    }
    
    void setAge(Integer age) {
        this.age = age;
    }
}

// Personを継承してWesternPersonを作る
// （extendsは継承を表すときに使われることが多いキーワード）
class WesternPerson extends Person {
    // WesternPersonのコンストラクタ
    WesternPerson(String givenName, String familyName, Integer age) {
        // 継承元のPersonのコンストラクタを呼んで初期化
        // （superは継承元のクラスを表すときに使われることが多いキーワード）
        super(givenName, familyName, age);
    }

    // PersonではなくここでgetFullNameを実装
    String getFullName() {
        return this.givenName + " " + this.familyName; // 名 姓
    }
}

// Personを継承してEasternPersonを作る
class EasternPerson extends Person {
    // EasternPersonのコンストラクタ
    EasternPerson(String givenName, String familyName, Integer age) {
        // 継承元のPersonのコンストラクタを呼んで初期化
        super(givenName, familyName, age);
    }
    
    // PersonではなくここでgetFullNameを実装
    String getFullName() {
        return this.familyName + " " + this.givenName; // 姓 名
    }
}

WesternPerson westernPerson = new WesternPerson("Albert", "Einstein", 26);
EasternPerson easternPerson = new EasternPerson("信長", "織田", 47);

println(westernPerson.getFullName()); // Albert Einstein
println(easternPerson.getFullName()); // 織田 信長
```

このように、継承を使えば共通処理を一箇所にまとめて書くことができます。

クラスがあるクラスを継承した場合、継承元のクラスを **_スーパークラス_** 、継承先のクラスを **_サブクラス_** と呼びます。上の例では、 `Person` は `WesternPerson` と `EasternPerson` のスーパークラスであり、 `WesternPerson` と `EasternPerson` は `Person` のサブクラスです。

## ポリモーフィズム

`WesternPerson` と `EasternPerson` をまとめて扱いたいときはどうすれば良いでしょうか。例えば、西洋人と東洋人が混ざった会員一覧を表示したいとしましょう。

実は、 `WesternPerson` オブジェクトと `EasternPerson` オブジェクトは `Person` 型の変数に代入することができます。

```java:(K)
// Person型変数に代入可
Person westernPerson = new WesternPerson("Albert", "Einstein", 26);
Person easternPerson = new EasternPerson("信長", "織田", 47);
```

異なる型の変数にオブジェクトを代入できるのは気持ち悪いですね。こんなことがなぜ許されるのでしょうか。

クラスを継承したときにメソッドを追加することはできますが、スーパークラスが持っているメソッドをなくすことはできません。そのため、サブクラスはスーパークラスのすべてのメソッドを持っていることが保証されます。つまり `WesternPerson` も `EasternPerson` も、 `Person` の持つどのメソッドを呼ばれても、そのメソッドを持っているので `Person` として振る舞うことができるのです。だから、 `Person` 型の変数に `WesternPerson` オブジェクトや `EasternPerson` オブジェクトが代入できても問題は起こりません。

このことを、

- WesternPerson is a Person.
- EasternPerson is a Person.

と言えることから、**_is-a関係_** と呼ぶこともあります。

`WesternPerson` も `EasternPerson` も `Person` として扱うことができるのなら、まとめて `Person` 型の配列に入れて会員一覧を表示できそうです。

```java:(L)
// 西洋人と東洋人が混ざった会員の配列
Person[] people = [
    new WesternPerson("Albert", "Einstein", 26),
    new EasternPerson("信長", "織田", 47),
    new WesternPerson("Isaac", "Newton", 43),
    new EasternPerson("秀吉", "豊臣", 61),
    new WesternPerson("Galileo", "Galilei", 46),
    new EasternPerson("家康", "徳川", 73),
];

// peopleの要素を一つずつpersonに代入して実行されるfor-eachループ
for (Person person : people) {
    println(person.getFullName()); // PersonはgetFullNameを持たないのでコンパイルエラー！！
}
```

さて、困りました。上記のコードでは `Person` 型の `person` は `getFullName` を持っていないので「 `Person` クラスには `getFullName` というメソッドはありません。」というコンパイルエラーになってしまいます。 `person` には `WesternPerson` オブジェクトや `EasternPerson` オブジェクトが代入されるので実際には `getFullName` を持っているのですが、 `person` に代入されるオブジェクトが何かは実行時に決まることであり、コンパイル時にそれを知ることはできません。コンパイラには `person` に代入されるオブジェクトが本当に `getFullName` を持っているかわからないので、コンパイルエラーになってしまうのです。

そこで、次のように `Person` クラスにも `getFullName` メソッドを実装してみるとどうでしょう？そうすればコンパイラは `Person` クラスにも `getFullName` メソッドがあると知ることができます。

```java:(M)
class Person {
    ...
    String getFullName() {
        return null; // 無意味な値を返す
    }
}
```

このような状態で次のコードを実行すると何が起こるでしょうか。

```java:(N)
Person person = new WesternPerson("Albert", "Einstein", 26);

println(person.getFullName()); // null? "Albert Einstein"?
```

`Person` の `getFullName` は `null` を、 `WesternPerson` では『名 姓』を返します。 `Person` 型変数 `person` に代入された `WesternPerson` オブジェクトの `getFullName` を呼ぶと、 `Person` か `WesternPerson` か、どちらの `getFullName` が実行されるでしょうか。

実行結果は次のようになります。

```
Albert Einstein
```

スーパークラス（ `Person` ）とサブクラス（ `WesternPerson`, `EasternPerson` ）で同名のメソッドが実装されている場合、スーパークラスのメソッドを上書きして挙動を変更することができます。このことを、メソッドの **_オーバーライド_** と呼びます。また、上の例のように、メソッドがオーバーライドされていると変数の型に関係なくその変数に代入されているオブジェクトのメソッドが呼ばれます。このような挙動を示す性質のことを **_ポリモーフィズム_** と呼びます。

**(M)** のように `Person` にも `getFullName` を宣言し、それをオーバーライドすることで **(L)** のコードはコンパイルエラーを回避できます。その状態でコードを実行すると、ポリモーフィズムによって正しく会員一覧を表示することができます（↓）。同じ `Person` 型の変数 `person` に対して `getFullName` を呼んでいますが、 `WesternPerson` では『名 姓』の順に、 `EasternPerson` では『姓 名』の順に表示されます。

```
Albert Einstein
織田 信長
Isaac Newton
豊臣 秀吉
Galileo Galilei
徳川 家康
```

ポリモーフィズムによって `WesternPerson` と `EasternPerson` をより抽象的な `Person` としてまとめて扱えるようになりました（ `Person` では `getFullName` が『名 姓』だろうと『姓 名』だろうと、とにかくフルネームを返せば良いという意味で抽象的です）。

このような抽象化は、カプセル化・継承・ポリモーフィズムの組み合わせによって実現されています。カプセル化によって「実装」と「振る舞い」が分離され、継承時のオーバーライドによって「振る舞い」が書き換えられ、ポリモーフィズムによって実行時の「振る舞い」が変化することで抽象化が実現されているわけです。

カプセル化・継承・ポリモーフィズムはそれぞれ単体で意味を持ちますが、これらが組み合わさって抽象化が実現されることは、オブジェクト指向プログラミングにおいて特に重要なことだと筆者は考えています。

## 抽象クラスと抽象メソッド

**(M)** では、 `getFullName` で `null` を返すように実装しました。しかし、 `Person` オブジェクトを直接使うことはありません。 `Person` クラスは型として利用するだけで、オブジェクトを生成するのは必ず `WesternPerson` か `EasternPerson` なら、 `Person` クラスの `getFullName` メソッドの実装は何でもいいはずです。何でもいいのであれば次のように書きたいです。

```java:(O)
class Person {
    ...
    String getFullName(); // 実装自体を書かない
}
```

このように実装のないメソッドのことを **_抽象メソッド_** と呼びます。また、抽象メソッドを持つクラスのことを **_抽象クラス_** と呼びます[^5]。 Java や多くの言語では、抽象クラスには `abstract class` と、抽象メソッドには `abstract String getFullName();` というように、明示的に `abstract` のようなキーワードを付与します。

抽象クラスのオブジェクトを生成しようとするとコンパイルエラーになります。なぜなら、もし `Person` オブジェクトが生成できてしまったら、 `getFullName` メソッドが呼ばれたときに実装がないので困ってしまうからです。

```java:(P)
// Personは抽象クラスなのでPersonオブジェクトを生成することはできない
Person person = new Person("Albert", "Einstein", 26);
```

抽象クラスはそのオブジェクトを生成することができないので、継承して、抽象メソッドをオーバーライドして使うことが前提のクラスとなります。ただし、 `Person` オブジェクトは生成できませんが、 `Person` 型の変数は宣言できることに注意して下さい。 次のコードは正しいコードです。

```java:(Q)
// Personは抽象クラスだけどPerson型の変数は宣言できる
Person person = new WesternPerson("Albert", "Einstein", 26);
```

`Person` を型として使えることは、先程見たように、抽象化を実現するためには欠かせません。

抽象クラスに対して、抽象メソッドを持たない普通のクラスのことを **_具象クラス_** と呼びます。この例では、 `Person` は抽象クラス、 `WestenPerson` と `EasternPerson` は具象クラスになります。

## 抽象化の何がうれしいのか

`Person` と `getFullName` の例は単純すぎて抽象化の利点がわかりづらいかもしれません。より現実的な例として、ZIPファイルを展開して元のバイト列を復元する関数を考えてみましょう。

```java:(R)
// ZIPファイルを展開してバイト列を得る関数
Byte[] unzip(File file) {
    ...
}
```

もし、ファイルだけでなくZIP形式のバイト列を入力として展開したい場合はどうでしょう。次の関数も必要です。

```java:(S)
// ZIP形式のバイト列を展開してバイト列を得る関数
Byte[] unzip(Byte[] bytes) {
    ...
}
```

圧縮されたファイルを展開するには符号理論に基づいた複雑な処理が必要です。これらの二つの関数をバラバラに実装するのは大変です。

しかし、片方の `unzip` の中でもう片方の `unzip` を呼んで共通化することはできません。 **(R)** の `unzip` の中で **(S)** の `unzip` を呼ぶにはファイルの全体をメモリ上に読み込まないといけないですが、その場合。メモリに乗らないような巨大なファイルを処理できなくなってしまいます。逆に **(S)** の `unzip` の中で **(R)** の `unzip` を呼ぼうとすると、引数 `bytes` の中身を一度ファイルに書き出さないといけません。そのような無駄なファイルは作りたくありません。結局、二つの `unzip` をバラバラに実装することになってしまいます。

`unzip` を実装する上で重要なのは、引数で渡されたものがファイルであれバイト列であれ、先頭から順番にバイトデータを読み出せるということです。そこで、「先頭から順番にバイトデータを読み出せる」という振る舞いに `InputStream` という名前を付けて抽象化してみましょう。

```java:(T)
class InputStream {
    // 次の1バイトを取得するメソッド
    Byte read();
}
```

また、これを継承して `FileInputStream` と `ByteArrayInputStream` も作ります。名前の通り、前者はファイルから1バイトずつデータを読み出し、後者はバイト配列から1バイトずつデータを読み出す機能を提供します。

```java:(U)
class FileInputStream extends InputStream {
    // Fileを受け取り、InputStreamとして働く
    FileInputStream(File file) {
        ...
    }

    Byte read() {
        ... // ファイルから次の1バイトを取得
    }
}

class ByteArrayInputStream extends InputStream {
    // Byte配列を受け取り、InputStreamとして働く
    ByteArrayInputStream(Byte[] bytes) {
        ...
    }

    Byte read() {
        ... // 配列から次の1バイトを取得
    }
}
```

この `InputStream` クラスを使えば、 `unzip` 関数は次のようにまとめられます。

```java:(V)
// InputStreamから得られたZIP形式のデータを展開してバイト列を得る関数
Byte[] unzip(InputStream in) {
    ...
}
```

この方法であれば、二つの `unzip` 関数を別々に実装する必要はありません。加えて、 `HttpInputStream` や `StandardInputStream` を実装すれば、サーバーにおかれたZIPファイルや標準入力で渡されたZIPファイルなども処理できるようになります。 `unzip` 関数に一切手を加えることなしにです。

このように、プログラムの再利用性が飛躍的に高まることが抽象化のうれしいところです。

## まとめ

会員情報などを想定して人の情報（姓、名、年齢）を持つ `Person` クラスを例に挙げ、 **何のためにオブジェクト指向があり、どのように使うのか** を説明しました。特に、 **_カプセル化_** 、 **_継承_** 、 **_ポリモーフィズム_** を用いたプログラムの抽象化について説明しました。

本投稿では、オブジェクト指向の本質ではない次の項目を意図的に省略しています。そのうち補足の投稿を書くかもしれません。

- アクセス制御
- プロパティ
- 多重継承
- インタフェース
- 静的メソッド

[^1]: オブジェクト指向プログラミングの定義は明確ではないですが、本投稿では、カプセル化、継承、ポリモーフィズムによるオブジェクト指向について説明します。

[^2]: 経験則的に15分程度であれば電車移動などで連続して確保しやすいこと、あまり気合を入れなくても読み始められること、オブジェクト指向を説明するのに必要不可欠な分量から15分としました。本投稿の序文と「対象」、「まとめ」、コードを除いた文字数がおよそ6千数百字、本文中にアルファベットが多いことを考えると日本語換算で6000字程度の分量と思われます。日本人の平均読書スピードは600字／分ほどのようなので、本文に10分、コードに5分で想定しています。

[^3]: 本投稿のコードはJavaをベースにしていますが、オブジェクト指向を説明するのに都合が良いように改変した架空の言語で書かれています。

[^4]: 実はこのコードは正しくありません。 `givenName` や `familyName` にスペースを含む文字列が与えられるとおかしなことになります。しかし、そんなケースを考えても本筋とは関係なく話が複雑になるだけなのでここでは無視します。

[^5]: 抽象メソッドを一つも持たなくても、 `abstract` などのキーワードを明示的に付与することで抽象クラスにできる言語もあります。