デザインパターンの適用実例(その一)


初めに

「簡単なことを複雑にするのは容易だが、複雑なことを簡単にするのは難しいである」というのは僕の持論であります。

現場では、何かタスクが与えられたら、あんまり深く考えずにひたすらコードを並んで問題を解けようとするプログラマーをよく見かけます。そういうことを繰り返しやっているうちに、ソースコードがますます複雑になり読めなくなって、保守できないものになってしまう。

着手する前になるべく仕組みやアルゴリズムを検討した方が得策であります。そういうことによって、複雑に見える問題は(場合によっては劇的に)シンプルに見えるようになるのみならず、再利用性向上、生産性アップなどほかのメリットももたらします。

この場合、デザインパターンがよく役に立ちます。普段一般的な業務システムの機能実装では、共通機能はほとんどフレームワークや共通ライブラリでカバーすることとなっていますので、あんまりデザインパターンを利用する場面が少ないです。ただ、そのベースとなっているフレームワークや共通ライブラリにはよく利用されています。以下のパターンでは、馴染んでいる方も多いと思いますが、よく利用されているものとなります。

  • Singleton パターン
  • TemplateMethod パターン
  • FactoryMethod パターン

課題

製品開発の中での実例ですが、デザインパターンに知見がある方は以下の課題を解いてみてください。

昨今では、昔と違って業務システムの開発はOSSライブラリの利用に避けては通れないと言っても過言ではないでしょうか。OSSライブラリと言えば、メジャーなものもあるし、あんまり知られていないものもあります。どんなものであっても、時代の進化によってバージョンアップされたり淘汰されたりします。その場合、そのライブラリの変化になるべく影響を受けないためによく利用するデザインパターンがあります。

そこで問題:Javaの開発でデータビーンとJSON文字列との相互変換でGSONやJacksonなどのJSONのOSSライブラリを利用します。将来そのライブラリを別のライブラリに入れ替える可能性があるとします。利用しているライブラリのバージョンアップやほかのライブラリに入れ替えるなどの場合、既存ソースへの影響を最小限に抑えるにはデザインパターンをどう適用すればよいでしょうか。記事の後半を見る前にここで一度考えてみてください。

解決例

我々はデザインパターンのAdapterパターンを利用してこの課題を解決しています。これは唯一解ではないので、ほかのご意見がある方はぜひコメントしてください。
Adapterパターンの詳細を知りたい方はApaterパターン|TECKSCOREにご参考ください。

Adapterパターンを利用する場合は、以下の2種類の実装方法があります。

  • 継承を利用した実装
  • 委譲を利用した実装

将来別のライブラリに入れ替える可能性があるとの要件がありましたので、継承を利用すると、既存ソースへの影響が大きくなります。こちらでは「委譲を利用した実装」を採用しています。

実装サンプル

以下はその実装サンプルです。(記事のために簡略化したものです。)
将来別のライブラリに入れ替える場合も、利用側の実装はほぼ変更なしで対応できるはずです。

Targetインタフェース

package json;

import java.io.InputStream;
import java.io.OutputStream;

/**
 *
 * JSONのTargetインタフェース
 *
 */
public interface JsonAdapterIF {

    /**
     * Targetメソッド(JSON文字列の読み込み)
     */
    <T> T read(String json, Class<T> type) throws Exception;

    /**
     * Targetメソッド(JSON InputStreamの読み込み)
     */
    <T> T read(InputStream is, Class<T> type) throws Exception;

    /**
     * Targetメソッド(JSON文字列への書き込み)
     */
    String write(Object obj) throws Exception;

    /**
     * Targetメソッド(JSON OutputStreamへの書き込み)
     */
    void write(Object obj, OutputStream os) throws Exception;

}

Adapter

package json;

import java.io.InputStream;
import java.io.OutputStream;

import com.fasterxml.jackson.databind.ObjectMapper;

/**
 * JSON Adapterクラス(Jackson)。
 */
public class JacksonAdapter implements JsonAdapterIF {

    /** Adaptee (JacksonのObjectMapper) */
    private ObjectMapper om;

    /**
     * 利用側から直接Adapterのインスタンスを作れないようにprotected
     * 修飾子でコンストラクタを隠蔽しています。
     */
    protected JacksonAdapter() {
        this(new ObjectMapper());
    }

    /**
     * 利用側から直接Adapterのインスタンスを作れないようにprotected
     * 修飾子でコンストラクタを隠蔽しています。
     */
    protected JacksonAdapter(ObjectMapper om) {
        // the om must not be null
        assert om != null;
        this.om = om;
    }

    @Override
    public <T> T read(String json, Class<T> type) throws Exception {
        return om.readValue(json, type);
    }

    @Override
    public <T> T read(InputStream is, Class<T> type) throws Exception {
        return om.readValue(is, type);
    }

    @Override
    public String write(Object obj) throws Exception {
        return om.writeValueAsString(obj);
    }

    @Override
    public void write(Object obj, OutputStream os) throws Exception {
        om.writeValue(os, obj);
    }

}

Factory(おまけ)

package json;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import com.fasterxml.jackson.databind.ObjectMapper;

/**
 * JSON Adapter作成用ファクトリクラス。
 * クライアント側で直接Adapterのインスタンスを作れないため、ファクトリを提供する。
 */
public class JsonAdapterFactory {

    /** A thread safe map of JsonAdapter */
    private static Map<Class<?>, JsonAdapterIF> omMap = new ConcurrentHashMap<>();

    static {
        // デフォルトのJSON Adapter(任意のクラス用)
        omMap.put(Object.class, new JacksonAdapter());

        // 特別の初期化処理が必要あれば、別のObjectMapper を作成する
        ObjectMapper om = new ObjectMapper();
        // 略
        omMap.put(特別のクラス.class, new JacksonAdapter(om));
    }

    private JsonAdapterFactory() {
    }

    /**
     * デフォルトのJSON Adapterの取得。
     */
    public static JsonAdapterIF getJsonAdapter() {
        return omMap.get(Object.class);
    }

    /**
     * 特別のクラス用のJSON Adapterの取得。
     */
    public static JsonAdapterIF getJsonAdapter(Class<?> clazz) {
        return omMap.get(clazz);
    }

}

周@ソフトシンク