1. 概要

構造設計パターンは、大きなオブジェクト構造間の関係を特定することにより、それらの構造の設計を簡素化するパターンです。 それらは、ソリューションとして繰り返し可能になるように、クラスとオブジェクトを構成する一般的な方法を説明します。

Gang of Four は、そのような7つの構造的な方法またはパターンについて説明しています。 このクイックチュートリアルでは、Javaのいくつかのコアライブラリがそれぞれをどのように採用しているかの例を示します。

2. アダプター

アダプターは、その名前が示すように、は、他の点では互換性のないインターフェースをクライアントが期待するインターフェースに変換するための仲介役として機能します

これは、ソースコードを変更できない既存のクラスを取得して、別のクラスで機能させる場合に役立ちます。

JDKのコレクションフレームワークは、アダプタパターンの多くの例を提供します。

List<String> musketeers = Arrays.asList("Athos", "Aramis", "Porthos");

ここで、 Arrays#asListは、配列をリストに適合させるのに役立ちます。

I / Oフレームワークも、このパターンを広範囲に使用します。 例として、InputStreamをReaderオブジェクトにマッピングしているこのスニペットを考えてみましょう。

InputStreamReader input = new InputStreamReader(new FileInputStream("input.txt"));

3. ブリッジ

ブリッジパターンを使用すると、抽象化と実装を分離できるため、相互に独立して開発できますが、共存および相互作用する方法またはブリッジがあります

Javaでのこの例は、 JDBCAPIです。 これは、Oracle、MySQL、PostgreSQLなどのデータベースとそれらの特定の実装との間のリンクとして機能します。

JDBC APIは、 Driver Connection ResultSet、などの標準インターフェースのセットです。 これにより、さまざまなデータベースベンダーが個別に実装できるようになります。

たとえば、データベースへの接続を作成するには、次のように言います。

Connection connection = DriverManager.getConnection(url);

ここで、 url は、任意のデータベースベンダーを表すことができる文字列です。

例として、PostgreSQLの場合、次のようになります。

String url = "jdbc:postgresql://localhost/demo";

そしてMySQLの場合:

String url = "jdbc:mysql://localhost/demo";

4. コンポジット

このパターンは、オブジェクトのツリーのような構造を扱います。 このツリーでは、個々のオブジェクト、または階層全体が同じように扱われます。 簡単に言うと、このパターンは、クライアントが全体のいずれかの部分をシームレスに操作できるように、オブジェクトを階層的に配置します。

AWT / Swingのネストされたコンテナーは、コアJavaでの複合パターンの使用法の優れた例です。 java.awt.Container オブジェクトは、基本的に他のコンポーネントを含むことができるルートコンポーネントであり、ネストされたコンポーネントのツリー構造を形成します。

このコードスニペットを検討してください。

JTabbedPane pane = new JTabbedPane();
pane.addTab("1", new Container());
pane.addTab("2", new JButton());
pane.addTab("3", new JCheckBox());

ここで使用されるすべてのクラス( JTabbedPane JButton JCheckBox 、および JFrame )は、Containerの子孫です。 ]。 ご覧のとおり、このコードスニペットは、子を処理するのと同じ方法で、2行目のツリーまたはコンテナのルートを処理します。

5. デコレータ

このパターンは、元のオブジェクト自体を変更せずにオブジェクトの動作を強化したい場合に役立ちます。 これは、同じタイプのラッパーをオブジェクトに追加して、オブジェクトに追加の責任を付加することによって実現されます。

このパターンの最も普及している使用法の1つは、java.ioパッケージにあります。

BufferedInputStream bis = new BufferedInputStream(new FileInputStream(new File("test.txt")));
while (bis.available() > 0) {
    char c = (char) bis.read();
    System.out.println("Char: " + c);
}

ここで、 BufferedInputStreamはFileInputStreamを装飾して、入力をバッファリングする機能を追加しています。 特に、これらのクラスは両方とも、共通の祖先としてInputStreamを持っています。 これは、装飾するオブジェクトと装飾されるオブジェクトの両方が同じタイプであることを意味します。 これは、デコレータパターンの紛れもない指標です。

6. ファサード

定義上、ファサードという言葉は、オブジェクトの人工的または誤った外観を意味します。 プログラミングに適用すると、同様に複雑なオブジェクトのセットに別の面、つまりインターフェイスを提供することを意味します。

このパターンは、サブシステムまたはフレームワークの複雑さを単純化または非表示にしたい場合に役立ちます。

FacesAPIのExternalContextは、ファサードパターンの優れた例です。 内部でHttpServletRequest HttpServletResponse HttpSessionなどのクラスを使用します。 基本的に、これはFacesAPIが基盤となるアプリケーション環境を幸いにも認識できないようにするクラスです。

Primefaces が、実際には知らないうちに、これを使用してHttpResponseを作成する方法を見てみましょう

protected void writePDFToResponse(ExternalContext externalContext, ByteArrayOutputStream baos, String fileName)
  throws IOException, DocumentException {
    externalContext.setResponseContentType("application/pdf");
    externalContext.setResponseHeader("Expires", "0");
    // set more relevant headers
    externalContext.setResponseContentLength(baos.size());
    externalContext.addResponseCookie(
      Constants.DOWNLOAD_COOKIE, "true", Collections.<String, Object>emptyMap());
    OutputStream out = externalContext.getResponseOutputStream();
    baos.writeTo(out);
    // do cleanup
}

ここでわかるように、ファサードとして ExternalContext を使用して、応答ヘッダー、実際の応答、およびCookieを直接設定しています。 HTTPResponseは画像にありません

7. フライ級

フライウェイトパターンは、オブジェクトをリサイクルすることで、オブジェクトの重量またはメモリフットプリントを取り除きます。 つまり、このパターンに従って状態を共有できる不変オブジェクトがある場合は、それらをキャッシュしてシステムパフォーマンスを向上させることができます。

Flyweightは、JavaのNumberクラス全体で見つけることができます。

任意のデータ型のラッパークラスのオブジェクトを作成するために使用されるvalueOfメソッドは、値をキャッシュし、必要に応じてそれらを返すように設計されています。

たとえば、 Integerには静的クラスIntegerCache、があり、valueOfメソッドが常に-128から127の範囲の値をキャッシュするのに役立ちます。

public static Integer valueOf(int i) {
    if (i >= IntegerCache.low && i <= IntegerCache.high) {
        return IntegerCache.cache[i + (-IntegerCache.low)];
    }
    return new Integer(i);
}

8. プロキシ

このパターンは、別の複雑なオブジェクトへのプロキシまたは代替を提供します。 ファサードに似ているように聞こえますが、ファサードがクライアントと対話するための異なるインターフェイスを提供するという意味で、実際には異なります。 プロキシの場合、インターフェースはそれが隠すオブジェクトのインターフェースと同じです。

このパターンを使用すると、作成前または作成後に元のオブジェクトに対して任意の操作を実行することが容易になります。

JDKは、プロキシ実装用にすぐに使用できるjava.lang.reflect.Proxyクラスを提供します。

Foo proxyFoo = (Foo) Proxy.newProxyInstance(Foo.class.getClassLoader(),
  new Class<?>[] { Foo.class }, handler);

上記のコードスニペットは、インターフェイスFooのプロキシproxyFooを作成します。

9. 結論

この短いチュートリアルでは、コアJavaに実装された構造デザインパターンの実際の使用法を見ました。

要約すると、7つのパターンのそれぞれが何を表すかを簡単に定義し、コードスニペットを使用してそれらを1つずつ理解しました。