普段よく使うJavaライブラリにも、GoFのデザインパターンが隠されています。日々の作業が忙しく見逃しがちですが、たまにはじっくり一種の芸術ともいえる美しい設計を味わってみましょう。
今回の芸術
import org.apache.http.client.fluent.Request;
import java.io.IOException;
...
String html = null;
try {
html = Request.Get("http://www.qualysite.co.jp/")
.connectTimeout(1000)
.socketTimeout(1000)
.execute()
.returnContent()
.asString();
} catch (IOException e) {
...
}
引数で指定したURLのソース(HTML)を取得するシーンですが、あらためて鑑賞してみると、直感的に伝わってくるインターフェイスの美しさにうっとりしてしまいます。
鑑賞のポイント
流れるようなインターフェイスに目が行きがちですが、設計者に言わせると「あんなの飾りです。偉い人にはそれがわからんのですよ1」という答えが返ってきそうです。ここでの鑑賞ポイントは、何かとステップが多いHTTPの接続ルーティーンを、わかりやすく、シンプルにまとめた機能美です。以下で一緒に味わっていきましょう。
Facadeパターンを使わない場合
冒頭のコードは、Javaで最も有名だと思われるHTTPクライアントApache HttpComponents HttpClientの使用例です。このライブラリには、2013年にリリースされたバージョン4.3で、冒頭のようなインターフェイスが追加されました。
それ以前は、同じ結果を得るのに以下のような、ステップが多いコードを記述する必要がありました。
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import org.apache.http.HttpEntity;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
...
String html = null;
CloseableHttpClient httpClient = HttpClients.createDefault();
HttpGet httpGet = new HttpGet("http://www.qualysite.co.jp/");
// Requestの設定
RequestConfig requestConfig = RequestConfig.custom()
.setSocketTimeout(1000)
.setConnectTimeout(1000)
.build();
httpGet.setConfig(requestConfig);
CloseableHttpResponse response = null;
try {
response = httpClient.execute(httpGet);
// Content取得(EntityオブジェクトがContentを保持している)
HttpEntity entity = response.getEntity();
// Contentからソース取得
html = EntityUtils.toString(entity, StandardCharsets.UTF_8);
// Entity消費
EntityUtils.consume(entity);
} catch(IOException e) {
...
} finally {
try {
if (response != null) {
// Responseクローズ
response.close();
}
} catch (IOException e) {
...
}
}
このようなコードでは、コーディングに手間がかかることは表面的な問題です。本当の問題は、コードを記述するときにミスが起きやすいことです。HTTP接続を開始してから終了するまで、いくつものステップを順番に踏まないといけないことと、必ず実行しなければいけないステップがどれなのかわかりにくいことは、ミスの原因になります。
具体的には、
// Entity消費
EntityUtils.consume(entity); // 内部ではInputStreamを閉じている
や、
// Responseクローズ
response.close(); // 内部ではHTTPコネクションを閉じている
は、ビルド時にも実行時にもエラーにならないので、内部の作りを理解していないと簡単に書き忘れてしまうでしょう。「Entityは消費(consume)しないといけない」とか、「Responseは閉じないといけない」というのは一般的ではありません。この書き忘れは、リソースリークを引き起こします。
また、CloseableHttpClient
はCloseableインターフェイスを実装しているため、httpClient.close();
という記述もできるのですが、このようにHttpClientをcloseするメソッドは呼ばなくても大丈夫なのでしょうか? 公式のサンプルソースでは呼ばれていないので不要かと思いますが、不安になってしまいます。
低レベルでたくさんの機能を提供していて、数々のライブラリで使われているHttpClientですが、必然性がないケースでこのコードをそのまま使うのは美しいとは言えませんよね。
Facadeパターンを使った場合
冒頭のコードはFacadeパターンが使われています。コメント付きでもう一度見てみましょう。
import org.apache.http.client.fluent.Request;
import java.io.IOException;
...
String html = null;
try {
html = Request.Get("http://www.qualysite.co.jp/")
.connectTimeout(1000) // Requestの設定
.socketTimeout(1000) // Requestの設定
.execute()
.returnContent() // Content取得 -> Reponseクローズ -> Entity消費
.asString(); // Contentからソース取得
} catch (IOException e) {
...
}
注目すべきは、Response#returnContent
メソッドです。このメソッドひとつで、Content取得 -> Reponseクローズ -> Entity消費という、複数のオブジェクトを使う処理で必ず実行しなければいけないステップを、順序通り、まとめて処理できます。
このメソッドでは3つの処理を行っていますが、メソッドの名前がreturnContent
なのもポイントです。クラスの設計者は、ReponseクローズとEntity消費の処理をクラスの利用者に意識させたくなかったのでしょう。メソッドに含まれていないからです。ただ、さらに想像すると、getContent
ではなくreturnContent
としているところが、「単にContentオブジェクトの参照を返すだけではないんだよ」という隠れたメッセージも見えてくるような気がします。
Facade(ファサード)は、「建物の正面」という意味です。Facadeパターンの目的は、利用者に建物の正面しか見せないようにすること、つまり複数のクラスのごちゃごちゃしたインターフェイスをまとめて、最小限だけ見せることです。どうりでインターフェイスがエレガントだと思っていましたが、それは、設計者によって計られた美しさだったのです。
Facadeパターンへの専門家のコメント
多くの専門家からも、Facadeパターンを評価するコメントが寄せられています。(太字での強調は私がやりました。)
結城浩さん
あるプログラマが「ああ、このクラスを呼ぶ前にはこっちを呼ぶんだよ。こっちのメソッドの呼び出し前には、このクラスに登録しておく必要があるんだぜ」といった話を「得意げ」に語るときには、Facade役を導入する必要があることを示唆しています。
『Java言語で学ぶデザインパターン入門』 より
lang_and_engineさん
複数のクラスを呼び出す際,呼び出す順番を思い出さなくてすむように,定石として1メソッド内に集約しておく。そのメソッドは,いわば複数のクラスの利用手順書になる。
GoFの23のデザインパターンを,Javaで活用するための一覧表 より
最後に
わざわざ美術館に行かなくても、たった1行のコードを眺めるだけで知的な愉しみを味わうことができるのは、プログラマーの醍醐味でしょう。
Facadeパターンの芸術性に共感してくださったエンジニアの方は、ぜひ当社(クオリサイトテクノロジーズ株式会社)の採用担当までご連絡ください!
関連記事
インスタンスを作る
- よく使うJavaライブラリで味わうデザインパターン - Factoryパターン
- よく使うJavaライブラリで味わうデザインパターン - Builderパターン
- よく使うJavaライブラリで味わうデザインパターン - Abstract Factoryパターン
インターフェイスをシンプルにする
- よく使うJavaライブラリで味わうデザインパターン - Facadeパターン
- よく使うJavaライブラリで味わうデザインパターン - Adapterパターン
他のクラスに任せる
参考URL
- HttpClient Quick Start - Apache Software Foundation
- HttpClient Fluent API - Apache Software Foundation