Observerパターンの基本を、クラス図とコードを使って説明

おす、やまうちです。
エンジニア歴3ヶ月弱なうです。
偉くてすごい人達は朝早く起きて活動している聞いて、朝活をしております。
(いいと思ったものは積極的に取り入れていくスタイル)
ちなみに、これ書いてるのは朝じゃないですｗ

そして現在学習している本、『アジャイルソフトウェア開発の奥義』。
この本で、Observerパターンなるものが出てきました。
深く理解したわけではないのですが、1ミリでも深く理解するために自分の考えをまとめたいと思います！
まだまだ浅瀬なんですけどね。。。

[アジャイルソフトウェア開発の奥義](https://www.amazon.co.jp/dp/4797347783/ref=cm_sw_r_tw_dp_U_x_ynksEb403Q7NF)

## サンプルケース

とある学習塾にて、先生が生徒の学習時間を管理しているという設定。

### 一旦Observerパターンを使わないやつ

Observerパターンではない一番シンプルな設計が以下です。
![Untitled Diagram (17).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/281895/da207b40-6782-7d15-3d6c-b709fad449ef.png)
生徒が先生に報告(Report)しています。
先生の方では受け取った報告をもとに情報を更新(Update)します。

ちなみにですが、Observerパターンにおいて、「観察者」と「通知者」という考え方があり、今回の場合ですと「先生が観察者」で「生徒が通知者」になります。
コードはこんな感じ。

```java
class Student
{
    private Teacher teacher;
    private String studentName;

    public Student(Teacher teacher, String studentName)
    {
        this.teacher = teacher;
        this.studentName = studentName;
    }

    public void report(int studyTime)
    {
        teacher.update(this.studentName, studyTime);
    }

}

class Teacher
{
    public void update(String studentName, int studyTime){
        System.out.println(studentName + "の勉強時間は" + studyTime + "です");
    }
}
```

このくらいなら特に問題もなさそうですが、少し生徒を増やしてみましょう。
![Untitled Diagram (18).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/281895/87a3a3f7-71b3-eb91-a0fa-608e5ec2cb1c.png)
う～ん、まだセーフ？
生徒が増える分には問題は生じてないですね。
通知者である生徒それぞれに変化がないので。

ではちょっと問題がある感じにしていきましょう！

### 先生が増えたり変わると問題が発生
内容としてはいつもの先生(Teacher)が休みだったため、別の先生(TeacherB)に報告することにしましょう。
観察者の交代です。
こんな感じ。
![Untitled Diagram (19).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/281895/e7d8cbc3-eff2-ccf7-432a-e2a61829a9c7.png)

一番上のTeacherが**TeacherB**に変わってますね。
そこまで大きな変化はないように見えますが、そんなことはありません。
報告(report)する先生を変える必要がありますので、こんなことが必要になります。

```java
// Studentクラスに追加
public void setTeacher(Teacher teacher)
{
    this.teacher = teacher;
}

studentA.setTeacher(teacherB);
studentB.setTeacher(teacherB);
studentC.setTeacher(teacherB);
```

う～ん、問題あり！ｗ

これでは先生が変わるたびに生徒の報告先を変えなければならないので、生徒が多ければ多いほど悲しみが深いです。。。
これに対応するのが、**Observerパターン**です！

## Observerパターンに変更

とりあえずクラス図を！
![Untitled Diagram (23).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/281895/aba62733-93d5-b00e-bb3d-871c6277b098.png)
いきなり雰囲気が変わりました(；・∀・)
StudentにもstudentNameとstudyTimeのゲッターを実装しました。
慌てず増えた`Subject`クラスと`Observer`クラスについて掘り下げましょう。

### Observerクラス

これは**観察者側に存在するインターフェース**です。
Observerという単語の意味が観察者ですね。
TeacherクラスはObserverインターフェースを実装しています。
Observerを通して、観察者である先生は通知者である生徒の報告を受け取ります。
ここは特に難しいことはないです。

```java
public interface Observer
{
    public abstract void update(Subject subject);
}
```

### Subjectクラス

こちらは色々書いてあるのでゆっくり見ていきましょう。
まずこれはStudentクラスの親になる、抽象クラスです。
この抽象クラスで、先生(Observer)を管理しています。
先生を管理する変数`observers`は、リストなので何人でも管理できます！

続いて`notifyObserver`というメソッドですが、こちらで上記のリストで管理している先生(Observer)全員に対して、生徒から受け取った報告をしていきます。
やっていることはこれだけです！ｗ

`addObserver`は先生を追加したいときに使うメソッドです。

他に定義しているメソッドは抽象メソッドで、子クラスであるStudentクラスで実装します。

```java
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public abstract class Subject
{
    // ここで先生を管理、リストなので何人でもOK
    public List<Observer> observerList = new ArrayList();

    abstract public void report(int studyTime);
    abstract public String getStudentName();
    abstract public int getStudyTime();

    protected void notifyObserver()
    {
        Iterator<Observer> i = observerList.iterator();
        while (i.hasNext())
        {
            Observer observer = i.next();
            observer.update(this);
        }
    }

    public void addObserver(Observer observer)
    {
        observerList.add(observer);
    }
}
```

流れとしてはこんな感じ
1. Studentクラスの`report`を実行
2. `report`メソッドの中で、親クラスの`notifyObserver`を呼び出す
3. `notifyObserver`メソッドの中で、`Observer`インターフェースの`update`を呼び出す
4. `Observer`インターフェースを実装している、`Teacher`クラスの`update`を実行

## まとめ

Observerパターンを取り入れることで、他を一切変更することなく、今回の例でいう先生をいくらでも増やせる状況を作れます。
神っ。
これを使いこなすことができればレベルがあがる気がする。
とはいえ本にはこうも書いてあった。
> Observerパターンを乱用すると、理解しにくく、流れを追いかけるのが難しいシステムになりやすい。
『アジャイルソフトウェア開発の奥義 P397』より引用

まじかよ。
使えるだけじゃだめで、タイミングまでも見極める必要があるとは。。。
先は長い(；´Д｀)

## 最終的な全体のコード

### Observer.java
```java
public interface Observer
{
    public abstract void update(Subject subject);
}
```

### Teacher.java
```java
public class Teacher implements Observer
{
    public void update(Subject subject){
        String name = subject.getStudentName();
        int studyTime = subject.getStudyTime();
        System.out.println(name + "の勉強時間は" + studyTime + "分");
    }
}
```

### Subject.java
```java
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public abstract class Subject
{
    private static List<Observer> observerList = new ArrayList();

    abstract public void report(int studyTime);
    abstract public String getStudentName();
    abstract public int getStudyTime();

    protected void notifyObserver()
    {
        Iterator<Observer> i = observerList.iterator();
        while (i.hasNext())
        {
            Observer observer = i.next();
            observer.update(this);
        }
    }
    public void addObserver(Observer observer)
    {
        observerList.add(observer);
    }
}
```

### Student.java
```java
public class Student extends Subject
{
    private String studentName;
    private int studyTime;

    public Student(String studentName)
    {
        this.studentName = studentName;
    }

    public String getStudentName()
    {
        return studentName;
    }

    public int getStudyTime()
    {
        return studyTime;
    }

    public void report(int studyTime)
    {
        this.studyTime = studyTime;
        notifyObserver();
    }
}
```

### Main.java(2パターンの実行)

```java
public class Main {
    public static void main(String[] args) {
        Teacher teacher = new Teacher();
        Student student = new Student("Mike");
        student.addObserver(teacher);
        student.report(10);
    }
}
```

```
Mikeの勉強時間は10分
```

```java
public class Main {
    public static void main(String[] args) {
        Teacher teacher = new Teacher();
        Teacher teacher1 = new Teacher();
        Student student = new Student("Mike");
        Student student1 = new Student("Bob");
        student.addObserver(teacher);
        student.addObserver(teacher1);
        student.report(10);
        student1.report(40);
    }
}
```

```
Mikeの勉強時間は10分
Mikeの勉強時間は10分
Bobの勉強時間は40分
Bobの勉強時間は40分
```

先生と生徒がそれぞれふえてもちゃんと動く！
(先生2人 × 生徒2人 で4件の出力)