Decorator Pattern


## 自己紹介

* 氏名: 政倉 智 (まさくら とも)
* 所属: codeArts 株式会社
* 所属: html5j 鹿児島
* 趣味: バイク

---

## アジェンダ

今日は、デザインパターンの話ってより

デザインパターンを使うと

こういうことができるよということを

覚えていただければ嬉しいです

デザインパターンの形とかより

メリットを理解していきましょう

---

## サンプルコード


---

GitHub のリポジトリを検索するコンソールアプリ

https://github.com/masakura/decorator-sample

---

動かしてみる

```
$ ./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 の検索キーワードを入力してください:
```

---

こんな感じ

![](http://www.plantuml.com/plantuml/svg/Iyv9B2vMy4tCp5Eevb800krS85iZiIYzM9Ehbeih6ImVcWcJBKvDB4hE24ejo2_EBCaloamjrW2foyilILL8oYyfoSy5gtpQklHfr-p7JPEUzazxtBZuSUEo9piQNYnR3XHviN35yoMxW0oWikSxDpnkwdgnVyhZnfa8szmpIpnAanmBy5cQdLfPdb-KWirkY7O5nm1F1P039kvvtBZvQVkknutJWSu0MRfrmhDvSq0kWTiBzhzkKMPwHeck7T2tN40Fmxbmvm00)

---

処理の流れはこんな感じ

![](http://www.plantuml.com/plantuml/svg/oydFB-1I08BGJ0LTEo1ffP2UMkAPLr1Qeg79XIJZdbdYKPhaM01KK9oQM9IS4PHQa5-SMP9Vb9bQh96TMbcUNvIY2TO0LwgW2pMoABMuDAU42ysYbCpYWfp4IWs8h2PNQbuA0000)

---

GitHub API の呼び出しはこんな感じ

```java
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 を呼び出して結果を返してるだけ!

---

入力を受け付けてるのはこんな感じ

```java
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());
        }
    }
}
```

---

## キャッシュを組み込む

---

本題

ちょっと遅いので、キャッシュしたくなりました!

皆さんどうします?

---

ここ、いじりたくなりませんか?

```java
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 クラスを修正すると...

![](http://www.plantuml.com/plantuml/svg/Iyv9B2vMSCyiyIXDSYp8LAZcgkNAooz9LIZ9Bm1o583WSUDa46gQz6J_kcTh5pzkdFcugVjnumezFJs8VFE4nqqR7ZVsF6tV1PRfVjpKf0WqXoiPR7Weagb5cUaP9QZcDgv_tDW2HXCVDwv4QFCloKWjCiIkzLbtwzEzaryqj09j1Giydx_qSLFduyPLO8FQ7ZSkVDou6cfB5yX5ZTCVD_Q3v9kwqQmmmk6wfhMFcnS_Rcv-tBJzSVDIi07peMOqja2DNWxqwUE6HZoseviozN5J3qY-gHce4EGIOEZebXResY7YW0lgSwp2HkZ20000)

---

Ui クラスを修正すると...

![](http://www.plantuml.com/plantuml/svg/Iyv9B2vMSCyiyIXDSYp8LAZcgkNYIWOBXa9uUVabgGf5cUaP9K2k0X0yRfmCGasJdknVzspjuiUDCnytTJzkNA6dfuVGx5-IaLfaYDs09xkdFMvSzsJNhiUDgnytDJsTqFEuSGRKiAP942NxvhvSi0TcDD2-5I2rZQk0NB3yo_RU5qlQ7pVsF9yo_t7ZliTDFI0PLDkD89u1UaEtj43N3k59BgZZi0eReGi30000)

---

コピペすると...

![](http://www.plantuml.com/plantuml/png/Iyv9B2vMSCulo4nDSSyiyIXDSYp8LAZcgkNAooz9LIZ9Bm1o583WSTFcnyrJ7ZVlUTmqwSdUEOyR5pzkN0QMUR9_tRErYu4wk9B1Xk4tfd4o6Arxt3pXSTC6YDx7ZSl1akZEUDou6Yp-RCMgfnkdlfpM3TJtT7_hYsMDZnjxG1P2RGo5M0Nb-PTaf1OPOeWOGKIWihgb1Rfs2AToGPMW2tC1ZOQ8eRaD0000)

---

どれがいいんだろうね...

将来どうなるかを考えてもっとも適切なものを...

選ばずに

---

なるべく書き換えない方向で!

---

こうじゃなくて!

![](http://www.plantuml.com/plantuml/svg/2yrKqBLJSCyiyIXDSYp8j58eJaqiIiu8IYt8ByuioI_AJ2tMo4wjBCylIj7aoiilILKeoapFAE5I00AuBeN7JPCUDwr_tBpoSTFE9_lNFLkx-77ZpCTDtLofUIaA85rSN0Z5ka0BGu4s5QKMb-QK0000)

---

こうでもなくて!

![](http://www.plantuml.com/plantuml/svg/2yrKqBLJSCyiyIXDSYp8j58eJaqiIiu8IYt8ByuioI_AJ2tMo4wjBCylIj7aoiilILN8IKqhuL80Wj1CXSTDanuthN_SlF9nqyud-zSzMxluSUFCnqtTNAbvAGeWrLnSSACLT84MXG9jA4ejBiqf0G00)

---

こうです!

![](http://www.plantuml.com/plantuml/svg/2yrKqBLJS4vCpaXrpopnA4roBCYqKYXEJInApWXABSWlpYp9ByfCBTR8JgqipozAqUJYIimfKNZSjFPny-B7pSsFcxO_Rbxwk7aIYDtSmQK01AYcOJC_FRK4B8GmDJMdE1N3rCPLZnkdup2IIDU2D2Ffm-AMejJYqfmI8XrUZEPm7QXse0VoKhYuq0q0cX0Ajm80)

---

こんな感じ

![](http://www.plantuml.com/plantuml/svg/Iyv9B2vMSCyiyIXDSYp8LAZcgkNYIWOBEYScPwHYbGb5uVcNPAGM6MA86855yl9BKXLAyWk070KWWAZLXofutBW2oUh7ZUl1QfxiN_TixUB7ZUkUxjtuSlx-nqsJdlPFVzmuyN7ZgiUDBI1RWFRhbXResg4vaGlDnH0r26akg4lHnQ6kGHC60000)

---

ただ、Ui クラスに影響が出る

![](http://www.plantuml.com/plantuml/svg/Iyv9B2vMSCyiyIXDSYp8LAZcgkNYIWOBEYScPwHYbGc5yFBoIr8LYZBJCqg0N0KWG2X-tDZzkAaJtOZ7ZQl1wfxiN_TixUB7ZTCVDyv_kdVZo_dx7pTDUjeFv4vytDIDiCU_92Ero13PPh284KG4w0hTqW9TEpHRkD0iXQX1I7D1JKKNXngD9Wm0)

---

![](http://www.plantuml.com/plantuml/svg/Iyv9B2vMSCyiyIXDSYp8LAZcgkNYIWOBEYScPwHYbGc5yFBoIr8LYZBJCqg0N0KWGDFnk6aIaicF6zU15JtVDlDPv_x7ZIiUDyvytDJzk74ocBtySKCZMFxfaivdixS0LJ_Tk_7b_Fr0GvxDQNYweVzfppI83eGx_4ioKeiCaLq65Z62Y01TgLjQe6k7vYo4E488pU4WwHe2yiZIN527e8j3NOCc3000)

---

一行の修正だけで済む

```java
public class Ui {
    private final GitHubApi api;

    public Ui() {
        // api = new GitHubApi();
        api = new CacheGitHubApi(new GitHubApi());
    }

    // ...snip
}
```

---

![](http://www.plantuml.com/plantuml/svg/Iyv9B2vMSCyiyIXDSYp8LAZcgkNAooz9LIZAJCye0N8LWE1nqvB7pPiVD-r_tBpdSVFKnqtBGI9DEnythdYwRlwBnQsF6zU15JxPiV3PDA3mkgTxTpxjtFIif_5nu-B7ZSiUDtK31HytTJnkx7_SkEfnOuiYnE3cLI1ZcZQ3J6rSztplkcVziv_spN_QsVwuSVNZfgQNZLEVzlS-RfmECGJeEAvai8kT4vCpKb6T3PK9HU7vbsIa5XbYY1X1H81gTKiBTEtGZ4E88uJGB0H9rn2GHvRcWZe4NHpg6ZHX0000)

---

abstract クラスを使ってインターフェイスと実装を分離

![](http://www.plantuml.com/plantuml/svg/IqmgBYbAJ2vHICv9B2vMSCyiyIXDSYp8LAZcgkNAooz9LIZAJCye0N8LWE3fklal5hS-RfhqjBFhvSmbZnkdFsvS1fP9X9dpj6SQK2SNn3ZdnEICL5GpeJ91YUK4x498FsvS1xVuSUFanuqB7pTrFcvS_NpvxiUDCq5cejcZMrgWMsE7C84BnJOCMGpjkaYo0000)

するか

---

interface を使ってインターフェイスと実装を分離

![](http://www.plantuml.com/plantuml/svg/oymhIIrAIqnELN3FBF4eJN8io5IevghboiilILKeoapFA05o5O3WQV_w5yrx7pUkUjpK-RXnWq9YKd59KM9oYSBZflMFcrS-Rjhvk74zMENZfYMFcpS_Rjh_k7dFu-QfZvkMWqH0yYiUDqv_tBZioTxzZnlR7ZUkUzmqwMKZa3sHWBxr2fbmPpvkNFsuSURZfcwW8xcISnABYnMS4vCpKb6T3vK9IYn74qTJgrTQe5TZX_0x5ueE35bqxRgwIB80)

---

abstract class と interface どっちがいい?

* なるべく interface で!
* ただ、interface だと問題が出ることがある
    * Java は interface のデフォルト実装で解決
    * .NET Framework は拡張メソッドで解決

---

ハンズオンではこのあたりまでやります

---

メリット

* 使う側でぱちぱちできる
* すぐに元に戻せる
* 単体テストがやりやすい
* 後回しにできる

---

使う側でぱちぱちできる

```java
// キャッシュなし
api = new RawGitHubApi();

// キャッシュ付き
api = new CacheGitHubApi(new RawGitHubApi());

// API コールしたときにログを出力する
api = new CacheGitHubApi(new LogGitHubApi(new RawGitHubApi()));
```

---

すぐに元に戻せる

`CacheGitHubApi` クラスがバグってもそこまで怖くない!

```java
// api = new RawGitHubApi();
api = new CacheGitHubApi(new RawGitHubApi());
```

---

単体テストがやりやすい

手で、キャッシュされたかどうかのテストを行うのはすごく困難

```java
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 (おまけ)

---

ログ・キャッシュ・トランザクションは、似たようなコードをたくさん生み出す

```java
// こんな形のコードが大量に生まれる
log.start("foo.method1()");

try {
    foo.method1();
} catch (Exception ex) {
    log.exception(ex);
    throw ex;
}

log.end("foo.method1()");

```

---

これは decorator パターンを使っても解決できない

```java
class LogFoo extends Foo {}

class LogBar extends Bar {}

class LogBazz extends Bazz {}
```

---

AOP を使うと、decorator を自動で生成してくれる

大きく分けて二種類ある

* 実行時に動的に生成するタイプ
* コンパイル前にクラスを生成するタイプ

---

## 追加の課題

ハンズオンを受けるまでもないなって人は課題にチャレンジ

---

このハンズオンのアプリを三社 (A, B, C) に販売することになりました。でも、ちょっとずつ要望が異なります。

* A: そのままでいいよ!
* B: キャッシュは昔痛い目にあった。ネットワークを増強するから、キャッシュしないでくれ。
* C: キャッシュアルゴリズムは特許を持ってるから、こちら側でコード書くよ。あ、コードは開示できないからね!

---

## まとめ

* decorator パターンは利用する側がぱちぱちできる
* うまく使うと、いくつかの問題の解決を後回しにできる

---

### デザインパターンをうまく使えたときは楽しい!