Stateパターンのメリットを映画のレイティング判定で説明する

ユーザが見ることができる作品を、映画のレイティングシステムに従って選別できるように実装します。

映画のレイティングシステムとは、年齢制限する規制のことです。
G：全年齢
PG12：12歳以上
R15+：15歳以上
R18+：18歳以上
といった区分があります。

# クラス図
赤枠内がStateパターンで、それをUserが使用するという形をとっています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/190809/e19b86ca-9761-721c-ec1a-ee9cc8b7de86.png)
RatingSystemをインターフェースとし、G/PG12/R15で各制限を実装します。
R18が無いのは、R18の映画を扱っていないという想定です。

# メリット
・年齢制限をRatingSystemの実装クラス（G/PG/R15）に限定できる。（実装の局所化）
・R18映画を扱うことになり、仕様追加になっても容易に対応できる。（変更容易性）

# ソースコード
## interface RatingSystem
``` RatingSystem.java
package state.rating;

public interface RatingSystem {
    public boolean checkLimit(int age);
}
``` 

## class G
``` G.java
package state.rating;

public class G implements RatingSystem {

    @Override
    public boolean checkLimit(int age) {
        return true;
    }
}
``` 
## class PG12
``` PG12.java
package state.rating;

public class PG12 implements RatingSystem {

    @Override
    public boolean checkLimit(int age) {
        if(12 <= age)
            return true;
        return false;
    }
}
``` 

## class R15 
``` R15.java
package state.rating;

public class R15 implements RatingSystem {

    @Override
    public boolean checkLimit(int age) {
        if(15 <= age)
            return true;
        return false;
    }
}
``` 

## class Movie
``` Movie.java
package state.rating;

public class Movie {
    private String _title;
    private RatingSystem _rating;

    public Movie(String title, RatingSystem rating) {
        _title = title;
        _rating = rating;
    }

    public String title() {
        return _title;
    }

    public boolean checkLimit(int age) {
        return _rating.checkLimit(age);
    }
}
``` 

## class User
``` User.java
package state.rating;

public class User {
    private String _name;
    private int _age;

    User(String name, int age){
        _name = name;
        _age = age;
    }

    public String name() {
        return _name;
    }

    public int age() {
        return _age;
    }

    public void play(Movie movie) {
        System.out.println(movie.title() + "を再生します。");
        if(!movie.checkLimit(_age)) {
            System.out.println(" >>>年齢制限エラーのため、再生できません。");
            stop(movie);
        }
    }

    public void stop(Movie movie) {
        System.out.println(movie.title() + "を停止します。");
    }
}
``` 

## Main（実行）
``` Main.java
package state.rating;

public class Main {
    public static void main(String... args) {

        Movie torasan = new Movie("男はつらいよ・寅次郎夕焼け小焼け", new G());
        Movie titan = new Movie("進撃の巨人 ATTACK ON TITAN", new PG12());
        Movie jingi = new Movie("仁義なき戦い", new R15());

        User shinnosuke = new User("野原しんのすけ", 5);
        System.out.println(shinnosuke.name());
        shinnosuke.play(torasan);
        shinnosuke.play(titan);
        shinnosuke.play(jingi);
        System.out.println();

        User taro = new User("太郎", 12);
        System.out.println(taro.name());
        taro.play(torasan);
        taro.play(titan);
        taro.play(jingi);
        System.out.println();

        User hanako = new User("花子", 17);
        System.out.println(hanako.name());
        hanako.play(torasan);
        hanako.play(titan);
        hanako.play(jingi);
        System.out.println();
    }
}
``` 

## 実行結果
各ユーザの年齢によって、年齢制限に引っかかる映画は強制的に停止させられてしまいます。
>
野原しんのすけ
男はつらいよ・寅次郎夕焼け小焼けを再生します。
進撃の巨人 ATTACK ON TITANを再生します。
 \>>>年齢制限エラーのため、再生できません。
進撃の巨人 ATTACK ON TITANを停止します。
仁義なき戦いを再生します。
 \>>>年齢制限エラーのため、再生できません。
仁義なき戦いを停止します。
>
太郎
男はつらいよ・寅次郎夕焼け小焼けを再生します。
進撃の巨人 ATTACK ON TITANを再生します。
仁義なき戦いを再生します。
 \>>>年齢制限エラーのため、再生できません。
仁義なき戦いを停止します。
>
花子
男はつらいよ・寅次郎夕焼け小焼けを再生します。
進撃の巨人 ATTACK ON TITANを再生します。
仁義なき戦いを再生します。

# R18+の要件追加への対応
実務では仕様変更はよくあることです。
ここでは「R18映画も扱う追加要件が発生した」状況を想定しています。

「R18の映画も扱うことにしたから、改修してもらえますか？」
と言われたら、すぐに変更できた方がいいですよね？

Stateパターンであれば、R18クラスを追加すれば完了です。
年齢制限の判定処理（if文）をあちらこちらから探してきて修正する必要はありません。

## class R18
``` R18.java
package state.rating;

public class R18 implements RatingSystem {

    @Override
    public boolean checkLimit(int age) {
        if(18 <= age)
            return true;
        return false;
    }
}
``` 

## Main（実行）
``` Main.java 
package state.rating;

public class Main {
    public static void main(String... args) {

        Movie torasan = new Movie("男はつらいよ・寅次郎夕焼け小焼け", new G());
        Movie titan = new Movie("進撃の巨人 ATTACK ON TITAN", new PG12());
        Movie jingi = new Movie("仁義なき戦い", new R15());
        Movie devil = new Movie("悪魔の毒々モンスター", new R18());   // ※追記箇所

        User shinnosuke = new User("野原しんのすけ", 5);
        System.out.println(shinnosuke.name());
        shinnosuke.play(torasan);
        shinnosuke.play(titan);
        shinnosuke.play(jingi);
        shinnosuke.play(devil); // ※追記箇所
        System.out.println();

        User taro = new User("太郎", 12);
        System.out.println(taro.name());
        taro.play(torasan);
        taro.play(titan);
        taro.play(jingi);
        taro.play(devil);   // ※追記箇所
        System.out.println();

        User hanako = new User("花子", 17);
        System.out.println(hanako.name());
        hanako.play(torasan);
        hanako.play(titan);
        hanako.play(jingi);
        hanako.play(devil); // ※追記箇所
        System.out.println();

        // ※追記箇所
        User isekai = new User("異世界転生した大学生", 18);
        System.out.println(isekai.name());
        isekai.play(torasan);
        isekai.play(titan);
        isekai.play(jingi);
        isekai.play(devil);
        System.out.println();
    }
}
```

## 実行結果
上記のUserの中で、R18映画（悪魔の毒々モンスター）を見ることができるのは、
18歳である「異世界転生した大学生」だけです。
>
野原しんのすけ
男はつらいよ・寅次郎夕焼け小焼けを再生します。
進撃の巨人 ATTACK ON TITANを再生します。
 \>>>年齢制限エラーのため、再生できません。
進撃の巨人 ATTACK ON TITANを停止します。
仁義なき戦いを再生します。
 \>>>年齢制限エラーのため、再生できません。
仁義なき戦いを停止します。
悪魔の毒々モンスターを再生します。
 \>>>年齢制限エラーのため、再生できません。
悪魔の毒々モンスターを停止します。
>
太郎
男はつらいよ・寅次郎夕焼け小焼けを再生します。
進撃の巨人 ATTACK ON TITANを再生します。
仁義なき戦いを再生します。
 \>>>年齢制限エラーのため、再生できません。
仁義なき戦いを停止します。
悪魔の毒々モンスターを再生します。
 \>>>年齢制限エラーのため、再生できません。
悪魔の毒々モンスターを停止します。
>
花子
男はつらいよ・寅次郎夕焼け小焼けを再生します。
進撃の巨人 ATTACK ON TITANを再生します。
仁義なき戦いを再生します。
悪魔の毒々モンスターを再生します。
 \>>>年齢制限エラーのため、再生できません。
悪魔の毒々モンスターを停止します。
>
異世界転生した大学生
男はつらいよ・寅次郎夕焼け小焼けを再生します。
進撃の巨人 ATTACK ON TITANを再生します。
仁義なき戦いを再生します。
悪魔の毒々モンスターを再生します。

# まとめ
・状態による判定や機能を、特定のクラスに限定できる。（実装の局所化）
・仕様追加、変更に対する修正が比較的容易である。（変更容易性）