よく使うJavaライブラリで味わうデザインパターン - Adapterパターン

普段よく使うJavaライブラリにも、GoFのデザインパターンが隠されています。日々の作業が忙しく見逃しがちですが、たまにはじっくり一種の芸術ともいえる美しい設計を味わってみましょう。

## 今回の芸術

#### ソースファイル

```java:共通
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
...

Logger logger = LoggerFactory.getLogger(this.getClass());
...
```

#### クラスパスに追加するjarファイル[^1]

```text:パターン1　－　Logbackを使う場合
- slf4j-api-VERSION.jar
- logback-classic-VERSION.jar
- logback-core-VERSION.jar
```

```text:パターン2　－　Log4jを使う場合
- slf4j-api-VERSION.jar
- slf4j-log4j12-VERSION.jar
- log4j-VERSION.jar
```

```text:パターン3　－　Java標準のロガーを使う場合
- slf4j-api-VERSION.jar
- slf4j-jdk14-VERSION.jar
- rt.jar（Javaのコアライブラリなので最初からある）
```

Javaのロガー（SLF4J）を生成するシーンです。ソースコードはそのままで、クラスパスに追加するjarファイルを取り替えるだけで、ロガーの実装を自由に選ぶことができます。インターフェイスと実装をとてもきれいに切り離していることに芸術性を感じます。


## 鑑賞のポイント

Javaのロギングライブラリはいろいろある[^2]ため、プロジェクトごとに異なったものを利用していることも多いかと思います。他のプロジェクトのソースを自分のプロジェクトでも使おうとしたとき、利用されているロガーが異なると、ソースコードを修正しないといけなかったり、ロガーの設定ファイルを二重で管理しないといけなかったりと、美しくないことになってしまいます。

今回の鑑賞ポイントは、アダプタークラスを用意することで、実装クラス（ロガー）をすり替えても同じ使い方ができるようになっているところです。おまけに、実装クラスのすり替えはjarを置き換えるだけという、古典的ですが最もシンプルな方法が採用されています。設計の美しさはもとより、ライブラリを使う私たちに対しての設計者の粋な図らいに畏敬の念を抱きます。以下で一緒に鑑賞していきましょう。


## Adapterパターンを使わない場合

冒頭のコードをAdapterパターンを使わないで書くと、以下のようになります。

```java:パターン1　－　Logbackを使う場合
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.Logger;
...

LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
Logger log = lc.getLogger(this.getClass());
```

```java:パターン2　－　Log4jを使う場合
import org.apache.log4j.Logger;
...

Logger logger = Logger.getLogger(this.getClass());
```

```java:パターン3　－　Java標準のロガーを使う場合
import java.util.logging.Logger;
...

Logger logger = Logger.getLogger(this.getClass());
```

ロガーごとにインスタンス取得時の記述と、import文の記述が異なっています。また、インスタンス化する`Logger`クラスが異なるため、それぞれで使えるメソッドも異なります。この状態でロギングライブラリを変更すると、たくさんのソースコードを置換することになってしまいます。さらに、もし特定のロギングライブラリにしか存在しないメソッドを使っていた場合は、置換さえできません。



## Adapterパターンを使った場合

冒頭のコードではAdapterパターンが使われています。もう一度見てみましょう。

まずは、クラスパスに追加するjarファイルです。どのロギングライブラリにも3つのjarファイルがありますが、役割は同じで、

+ SLF4Jのインターフェイス（SLF4Jが配布しているjarファイル）
+ SLF4Jのインターフェイスの実装。内部でそれぞれのロギングライブラリを呼ぶ。アダプターの役割。
+ それぞれのロギングライブラリの実装

となっています。

ロギングライブラリの種類とjarの役割を表にすると以下のようになります。

|            | Logback | Log4j | Java標準のロガー |
|:-----------|:-----------|:------------|:-------------|
| **共通IF** | slf4j-api-VERSION.jar | 左に同じ | 左に同じ |
| **IF実装（アダプター）** | logback-classic-VERSION.jar | slf4j-log4j12-VERSION.jar | slf4j-jdk14-VERSION.jar |
| **ロギングライブラリ実装** | logback-core-VERSION.jar | log4j-VERSION.jar | rt.jar |

※IFはインターフェイスの略

そして、ロガーインスタンスは、どのロギングライブラリを使っていても、一律で次のように生成します。

```java
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
...

Logger logger = LoggerFactory.getLogger(this.getClass());
...
```

この`getLogger`メソッドの中身は以下のようになっています。

```java:org.slf4j.LoggerFactory.java
package org.slf4j;
...

// 共通IFのjarにあるクラス
public final class LoggerFactory {
    ...
    public static Logger getLogger(String name) {

        // org.slf4j.ILoggerFactoryのインスタンスを生成
        // 実装クラスはIF実装（アダプター）のjar内にあるクラス（下の表を参照）
        ILoggerFactory iLoggerFactory = getILoggerFactory();

        // org.slf4j.Loggerのインスタンスを生成
        // 実装クラスはIF実装（アダプター）のjar内にあるクラス（下の表を参照）
        return iLoggerFactory.getLogger(name);
    }
    ...
}
```

`ILoggerFactory`、および`Logger`の実装クラスは以下のようになっています。いずれのクラスもそれぞれのロギングライブラリのIF実装（アダプター）jarに含まれます。

|            | Logback | Log4j | Java標準のロガー |
|:-----------|:-----------|:------------|:-------------|
| **ILoggerFactoryの実装クラス** | ch.qos.logback.classic.LoggerContext | org.slf4j.impl.Log4jLoggerFactory | org.slf4j.jul.JDK14LoggerFactory |
| **org.slf4j.Loggerの実装クラス** | ch.qos.logback.classic.Logger | org.slf4j.impl.Log4jLoggerAdapter | org.slf4j.jul.JDK14LoggerAdapter |

このソースコードを追っていくと、次のような流れで`LoggerFactory`と`Logger`のインスタンスが生成されることがわかります。各クラスがどのjarに含まれているかを【】で書いています。それを意識すると理解しやすいです。

```text:Logbackの場合
org.slf4j.LoggerFactory【共通IFのjar】
　　　　　↓
　　　　　↓ org/slf4j/impl/StaticLoggerBinder.class ファイルを
　　　　　↓ 探してインスタンス化
　　　　　↓
org.slf4j.impl.StaticLoggerBinder【IF実装（アダプター）のjar】
　　　　　↓
　　　　　↓ org.slf4j.ILoggerFactoryインターフェイス
　　　　　↓ を実装しているLoggerContextをインスタンス化
　　　　　↓
ch.qos.logback.classic.LoggerContext【IF実装（アダプター）のjar】
　　　　　↓
　　　　　↓ org.slf4j.Loggerインターフェイス
　　　　　↓ を実装しているch.qos.logback.classic.Loggerをインスタンス化
　　　　　↓
ch.qos.logback.classic.Logger【IF実装（アダプター）のjar】
```

最終的に生成された`Logger`は、ロギングライブラリの実装jarに含まれるクラスではなく、IF実装のjarに含まれるクラスであることがポイントです。このクラスは、ロギングライブラリの参照をメンバ変数に保持していて、ログ出力のときにはそれを呼び出します。

以上のようにすることで、SLF4Jを使う人は、ロギングライブラリのインスタンスを直接参照するのではなく、そのアダプターに相当するIF実装クラスを参照して使用することになります。

```text:Logbackの場合
SLF4Jを使う人
　↓
　↓ 使う
　↓
org.slf4j.Logger【共通IFのjar】
この実体はch.qos.logback.classic.Logger【IF実装（アダプター）のjar】
　　　↓
　　　↓ 内部で使っている
　　　↓
　　ch.qos.logback.core.XXXAppender【ロギングライブラリ実装のjar】
```


## Adapterパターンのまとめ

Adapterパターンは、Wrapper（ラッパー）パターンと呼ばれることもあります。**そのままでは使いにくいクラスを使いやすいように適合させるために、新しいクラスでラップする**のです。ちなみに、そのままでは使いにくいクラスというのは、似たようなクラス間でメソッドに統一感がなかったり、ソースコードが汚かったりするクラスのことです。

どうりでSLF4Jのインターフェイスはきれいなはずです。それは、きれいにすることを目的に、Adapterパターンで整形された結果なのです。

今回の例にはAdapterパターンの魅力が最大限引き出されているものを選びましたが、ここまで徹底的にやらなくても（IF、IF実装、ロギングライブラリ実装でjarファイルを分けるまでやらなくても）、**既存のままでは使いにくいクラスのラッパークラスを作るだけで、Adapterパターンを使っている**ことになります。


## Adapterパターンへの専門家のコメント

多くの専門家からも、Adapterパターンを評価するコメントが寄せられています。

結城浩さん

> プログラムの世界でも、すでに提供されているものがそのままつかえないときに、必要な形に変換してから利用することがよくあります。「すでに提供されているもの」と「必要なもの」の間の「ずれ」を埋めるようなデザインパターン、これがAdapterパターンです。
> [『Java言語で学ぶデザインパターン入門』](https://www.amazon.co.jp/増補改訂版Java言語で学ぶデザインパターン入門-結城-浩/dp/4797327030/) より

lang_and_engineさん

>  Adapterパターンをうまく使えば，開発チーム内の対人関係が良くなる，という効用もある。低スキル者が滅茶苦茶なコードを書いた際，そのクラスのソースコード自体に横から手を出して他のメンバが書き変えようとすると，最初にコーディングした方のプログラマは「傷ついた」と感じるものだ。それを避けるために，ラッパークラスを一段かませて，ラッパークラス側を充実させて高品質にする。

> [GoFの23のデザインパターンを，Javaで活用するための一覧表](http://language-and-engineering.hatenablog.jp/entry/20120330/p1) より


## 最後に
わざわざ美術館に行かなくても、たった1行のコードを眺めるだけで知的な愉しみを味わうことができるのは、プログラマーの醍醐味でしょう。

Adapterパターンの芸術性に共感してくださったエンジニアの方は、ぜひ当社（[クオリサイトテクノロジーズ株式会社](http://www.qualysite.co.jp/recruiting/career/)）の採用担当までご連絡ください！


## 参考URL
- [Simple Logging Facade for Java (SLF4J)](https://www.slf4j.org/)
- [How to use Logback directly without going through SLF4J](http://stackoverflow.com/questions/9920336/how-to-use-logback-directly-without-going-through-slf4j)


## 関連記事
#### インスタンスを作る
- [よく使うJavaライブラリで味わうデザインパターン - Factoryパターン](http://qiita.com/takuya-s/items/7ffebaee2a9db4310460)
- [よく使うJavaライブラリで味わうデザインパターン - Builderパターン](http://qiita.com/takuya-s/items/3c3415962a7568c637ed)
- [よく使うJavaライブラリで味わうデザインパターン - Abstract Factoryパターン](http://qiita.com/takuya-s/items/6b26410d8eb47a9410e4)

#### インターフェイスをシンプルにする
- [よく使うJavaライブラリで味わうデザインパターン - Facadeパターン](http://qiita.com/takuya-s/items/eb663424a21094303b95)
- よく使うJavaライブラリで味わうデザインパターン - Adapterパターン

#### 他のクラスに任せる
- [よく使うJavaライブラリで味わうデザインパターン - Template Methodパターン](http://qiita.com/takuya-s/items/8898e9bae34acada6480)
- [よく使うJavaライブラリで味わうデザインパターン - Strategyパターン](http://qiita.com/takuya-s/items/473699b5cb2d5329eed3)


## 脚注
[^1]: 実際の開発シーンでは、jarファイルの追加には、Mavenなどのモジュール管理ツールを使います。Mavenの設定ファイル（pom.xml）への記述方法は、[こちらのSLF4Jの公式リファレンス](https://www.slf4j.org/manual.html#projectDep)を参照してください。

[^2]: [javaのロガーが多すぎて訳が解らないので整理してみました](http://www.bunkei-programmer.net/entry/2012/10/20/javaのロガーが多すぎて訳が解らないので整理して)