自己紹介
- 氏名: 政倉 智 (まさくら とも)
 - 所属: codeArts 株式会社
 - 所属: html5j 鹿児島
 - 趣味: バイク
 
アジェンダ
今日は、デザインパターンの話ってより
デザインパターンを使うと
こういうことができるよということを
覚えていただければ嬉しいです
デザインパターンの形とかより
メリットを理解していきましょう
サンプルコード
GitHub のリポジトリを検索するコンソールアプリ
動かしてみる
$ ./gradlew run -q
GitHub Repository の検索キーワードを入力してください:
aratana
mwtestlab/aratana => https://github.com/mwtestlab/aratana
yamakei7323/aratanaIntern => https://github.com/yamakei7323/aratanaIntern
tao1027/aratana-test => https://github.com/tao1027/aratana-test
High-Hill/aratana-intern => https://github.com/High-Hill/aratana-intern
takashi1029/aratana_intern => https://github.com/takashi1029/aratana_intern
papuaaaaaaa/yakitori => https://github.com/papuaaaaaaa/yakitori
AratanaLab/aratanalab.github.com => https://github.com/AratanaLab/aratanalab.github.com
abehazuki/knowlege => https://github.com/abehazuki/knowlege
GitHub Repository の検索キーワードを入力してください:
こんな感じ
処理の流れはこんな感じ
GitHub API の呼び出しはこんな感じ
public class GitHubApi {
    private final WebTarget target;
    public GitHubApi() {
        Client client = ClientBuilder.newClient();
        target = client.target("https://api.github.com/search/repositories");
    }
    public RepositoriesResult searchRepositories(String keyword) {
        return target
                .queryParam("q", keyword)
                .request(MediaType.APPLICATION_JSON_TYPE)
                .get(RepositoriesResult.class);
    }
}
GitHub API を呼び出して結果を返してるだけ!
入力を受け付けてるのはこんな感じ
public class Ui {
    private final GitHubApi api;
    public Ui() {
        api = new GitHubApi();
    }
    public void searchRepositories() {
        while (true) {
            String input = getInput();
            if (Objects.equals(input, "")) break;
            RepositoriesResult result = api.searchRepositories(input);
            display(result);
        }
    }
    private String getInput() {
        InputStreamReader is = new InputStreamReader(System.in);
        BufferedReader br = new BufferedReader(is);
        System.out.println("GitHub Repository の検索キーワードを入力してください: ");
        try {
            return br.readLine();
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
    }
    private void display(RepositoriesResult result) {
        for (Repository repo : result.getItems()) {
            System.out.println(repo.getFull_name() + " => " + repo.getHtml_url());
        }
    }
}
キャッシュを組み込む
本題
ちょっと遅いので、キャッシュしたくなりました!
皆さんどうします?
ここ、いじりたくなりませんか?
public class GitHubApi {
    private final WebTarget target;
    public GitHubApi() {
        Client client = ClientBuilder.newClient();
        target = client.target("https://api.github.com/search/repositories");
    }
    public RepositoriesResult searchRepositories(String keyword) {
        // ここをいじりたくなりませんか?
        return target
                .queryParam("q", keyword)
                .request(MediaType.APPLICATION_JSON_TYPE)
                .get(RepositoriesResult.class);
    }
}
個人的におすすめしません!
こういった修正を何度も繰り返すと混沌としてきます
ちょっとずつ解説します
GitHubApi クラスを修正すると...
Ui クラスを修正すると...
コピペすると...
どれがいいんだろうね...
将来どうなるかを考えてもっとも適切なものを...
選ばずに
なるべく書き換えない方向で!
こうじゃなくて!
こうでもなくて!
こうです!
こんな感じ
ただ、Ui クラスに影響が出る
一行の修正だけで済む
public class Ui {
    private final GitHubApi api;
    public Ui() {
        // api = new GitHubApi();
        api = new CacheGitHubApi(new GitHubApi());
    }
    // ...snip
}
abstract クラスを使ってインターフェイスと実装を分離
するか
interface を使ってインターフェイスと実装を分離
abstract class と interface どっちがいい?
- なるべく interface で!
 - ただ、interface だと問題が出ることがある
- Java は interface のデフォルト実装で解決
 - .NET Framework は拡張メソッドで解決
 
 
ハンズオンではこのあたりまでやります
メリット
- 使う側でぱちぱちできる
 - すぐに元に戻せる
 - 単体テストがやりやすい
 - 後回しにできる
 
使う側でぱちぱちできる
// キャッシュなし
api = new RawGitHubApi();
// キャッシュ付き
api = new CacheGitHubApi(new RawGitHubApi());
// API コールしたときにログを出力する
api = new CacheGitHubApi(new LogGitHubApi(new RawGitHubApi()));
すぐに元に戻せる
CacheGitHubApi クラスがバグってもそこまで怖くない!
// api = new RawGitHubApi();
api = new CacheGitHubApi(new RawGitHubApi());
単体テストがやりやすい
手で、キャッシュされたかどうかのテストを行うのはすごく困難
MockGitHubApi mock = new MockGitHubApi(); // テスト用モック
GitHubApi target = new CacheGitHubApi(mock);
// キャッシュがないので API がコールされた
target.searchRepositories("abc");
assertEquals(mock.callCount(), 1);
// キャッシュヒットしたので API がコールされない
target.searchRepositories("abc");
assertEquals(mock.callCount(), 1);
// 別のキーワードで検索はキャッシュヒットしない
target.searchRepositories("def");
assertEquals(mock.callCount(), 2);
後回しにできる
- API をどうキャッシュするかはかなり難しい問題
 - 今回、キャッシュなしからキャッシュありに書き換えた
 - ということは、プログラムを書く前に決める必要がなくなる
 - よりアプリの価値に近いところに集中しておいて、後から調整できる
 
decorator パターンの使い道
- キャッシュ・バッファリング
 - トランザクション
 - ログ
- 例: 監査のために、一時期のみメールを飛ばす
 - 設計時に織り込めなかったものでも対処しやすい
 
 - その場しのぎ
- 例: 特定パターンで計算が狂う
 - 根本的なバグを洗い出す前のその場しのぎに
 
 - 他にもたくさんありそうですね!
 
AOP (おまけ)
ログ・キャッシュ・トランザクションは、似たようなコードをたくさん生み出す
// こんな形のコードが大量に生まれる
log.start("foo.method1()");
try {
    foo.method1();
} catch (Exception ex) {
    log.exception(ex);
    throw ex;
}
log.end("foo.method1()");
これは decorator パターンを使っても解決できない
class LogFoo extends Foo {}
class LogBar extends Bar {}
class LogBazz extends Bazz {}
AOP を使うと、decorator を自動で生成してくれる
大きく分けて二種類ある
- 実行時に動的に生成するタイプ
 - コンパイル前にクラスを生成するタイプ
 
追加の課題
ハンズオンを受けるまでもないなって人は課題にチャレンジ
このハンズオンのアプリを三社 (A, B, C) に販売することになりました。でも、ちょっとずつ要望が異なります。
- A: そのままでいいよ!
 - B: キャッシュは昔痛い目にあった。ネットワークを増強するから、キャッシュしないでくれ。
 - C: キャッシュアルゴリズムは特許を持ってるから、こちら側でコード書くよ。あ、コードは開示できないからね!
 
まとめ
- decorator パターンは利用する側がぱちぱちできる
 - うまく使うと、いくつかの問題の解決を後回しにできる