自己紹介
- 氏名: 政倉 智 (まさくら とも)
- 所属: 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 パターンは利用する側がぱちぱちできる
- うまく使うと、いくつかの問題の解決を後回しにできる