Java
初心者
デザインパターン
GoF
Facade


はじめに

GoFのデザインパターンを紹介している『増補改訂版 Java言語で学ぶデザインパターン入門』を読んで、学んだ内容についてまとめます。


Facadeパターン


Facadeとは

日本語に訳すと「(建物の)正面」を意味します。

複数のクラスが絡むプログラムを使用する場合には、それぞれのクラスのメソッドを適切な順番で実行する必要があります。

例えば「処理Xを行いたい場合にはAクラスのBメソッドを呼んだ後に、CクラスのDメソッドとEメソッドを呼び、最後にFクラスのGメソッドを呼ぶ」などです。

上記の例ではクラスもメソッドもそれほど多くありませんでしたが、これがもっと多くの数になればなるほど、処理を行う際のクラスとメソッドの制御は複雑になっていきます。

このような場合に、処理を依頼する側から見た際に「窓口」となるインタフェース(API)を用意してあげるようなパターンのことをFacadeパターンと言います。

このパターンを適用することで、処理を依頼する側は複雑な処理の制御を把握する必要がなくなります。


登場人物

Facadeパターン使用するのは以下のクラス図に登場するクラスです。

image.png


実装クラス


  • Facade

    処理を行うためのクラスをまとめるクラスで、Clientに対しての窓口となります。

    実装すべき内容に関しては特に縛りなどはなく、難しい点もありません。


  • その他クラス

    処理を行うために呼び出されるクラスたちです。

    実装すべき内容に関しては特に縛りなどはありません。

    特段難しい点はありませんが、ポイントとしては使用されるクラスたちは窓口となるFacadeのことを意識しないことです。

    その他のクラスたちからFacadeのことを呼び出すことはないためです。


  • Client

    Facadeを呼び出すクラスです。

    実装すべき内容に関しては特に縛りなどはなく、難しい点もありません。



具体例

具体例として、以下のクラスをもとに説明します。

image.png


実装クラス



  • ListFacadeクラス



ListFacade.java

import java.io.FileWriter;

import java.io.IOException;
import java.util.Map;
import java.util.Properties;

public class ListFacade {
private ListFacade() { // newでインスタンス生成させないためにprivate宣言
}

public static void makeMemberList(String inputFileName, String outputFileName) {
try {
Properties memberProperties = MemberList.getProperties(inputFileName);
outputFileName = "C:\\work\\Facade\\src\\" + outputFileName + ".txt";
TxtWriter writer = new TxtWriter(new FileWriter(outputFileName));
Map<String, String> propertiesMap = writer.toMap(memberProperties);
writer.writeHeader();
writer.writeContents(propertiesMap);
writer.writeFooter();
writer.close();
System.out.println(String.format("ファイルを出力しました。ファイル名:%s", outputFileName));
} catch (IOException e) {
e.printStackTrace();
}
}

}


ListFacadeクラスはClientであるMainクラスの窓口となるクラスです。

statisなmakeMemberListメソッドのみが定義されており、MemberListクラスからプロパティを受け取り、そのプロパティをもとにMapを受け取り、ファイルに出力していることが分かります。

また処理が成功した場合にはファイルを出力した旨のメッセージをコンソールに出力しています。

ポイントとしては、これらの一連の処理の流れをMainクラスではなく、窓口であるListFacadeクラスに記述してあるという点のみです。

パターンの学習として、他に関しては特段難しい点はありません。



  • MemberListクラス



MemberList.java

import java.io.FileInputStream;

import java.io.IOException;
import java.util.Properties;

public class MemberList {
private MemberList() { // newでインスタンス生成させないためにprivate宣言
}

public static Properties getProperties(String fileName) { // ファイル名からPropertiesを得る
fileName = "C:\\work\\Facade\\src\\" + fileName + ".txt";
Properties properties = new Properties();
try {
properties.load(new FileInputStream(fileName));
} catch (IOException e) {
System.out.println(String.format("ファイルの読み込みに失敗しました。ファイル名:%s", fileName));
}
return properties;
}
}


2つあるその他のクラスの1つ目です。

staticなgetPropertiesメソッドのみが定義されており、ファイル名を受け取ることでプロパティファイルを返しています。

特段難しい点はありません。



  • TxtWriterクラス



TxtWriter.java

import java.io.IOException;

import java.io.Writer;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;

public class TxtWriter {
private Writer writer;

public TxtWriter(Writer writer) { // コンストラクタ
this.writer = writer;
}

// プロパティを受け取り値(キー)が25を超えるものをMapに格納
public Map<String, String> toMap(Properties properties) {
Map<String, String> propertiesMap = new HashMap<>();
for (Entry<Object, Object> e : properties.entrySet()) {
if (Integer.parseInt((String) e.getValue()) > 25) {
propertiesMap.put(e.getKey().toString(), e.getValue().toString());
}
}
return propertiesMap;
}

// ヘッダーを出力
public void writeHeader() throws IOException {
writer.write("***************Header***************");
writer.write(System.lineSeparator());
}

// 内容を出力
public void writeContents(Map<String, String> propertiesMap) throws IOException {
writer.write("年齢が25歳を超えるメンバーは以下です。");
writer.write(System.lineSeparator());
for (Entry<String, String> e : propertiesMap.entrySet()) {
writer.write(" ・名前:" + e.getKey() + " 年齢:" + e.getValue());
writer.write(System.lineSeparator());
}
}

// フッターを出力
public void writeFooter() throws IOException {
writer.write("***************Footer***************");
}

// 閉じる
public void close() throws IOException {
writer.close();
}

}


その他のクラスの2つ目です。

プロパティを受け取ってMapに格納するメソッドや、テキストファイルに出力するためのメソッドなどいくつかメソッドが定義されていますが、こちらに関しても特段難しい点はありません。


その他


  • テキストファイル


memberList.txt

#name=age

Tanaka_Tarou=25
Hoge_Hogeko=10
Yamada_Hanako=30
Neko_Nekota=50
Foo_Footarou=80

MemberListクラスの読み込みの対象となるテキストファイルです。

keyに名前、valueに年齢が当てはまります。


実行クラス


  • Mainクラス


Main.java

import java.io.FileWriter;

import java.io.IOException;
import java.util.Map;
import java.util.Properties;

public class Main {
public static void main(String[] args) {

// 1.Facadeパターンを適用した場合
ListFacade.makeMemberList("memberList", "outputFile1");

// 2.Facadeパターンを未適用の場合
try {
String inputFileName = "memberList";
String outputFileName = "outputFile2";

Properties memberProperties = MemberList.getProperties(inputFileName);
outputFileName = "C:\\work\\Facade\\src\\" + outputFileName + ".txt";
TxtWriter writer = new TxtWriter(new FileWriter(outputFileName));
Map<String, String> propertiesMap = writer.toMap(memberProperties);
writer.writeHeader();
writer.writeContents(propertiesMap);
writer.writeFooter();
writer.close();
System.out.println(String.format("ファイルを出力しました。ファイル名:%s", outputFileName));
} catch (IOException e) {
e.printStackTrace();
}

}
}


1.Facadeパターンを適用した場合だと、窓口であるListFacadeクラスに対してstaticなmakeMemberListメソッドを呼び出してあげるだけで、目的の処理を実行することができます。

また処理を複数回行いたい場合にも同様にメソッドを呼び出してあげるだけで済みます。、

2.Facadeパターンを未適用の場合の場合には、ListFacadeクラスに記述してある内容をそのまま記述しないと、目的の処理を実行することはできません。

また、処理を複数回行いたい場合には、処理を行いたい数だけ、複雑な処理を記述する必要があります。

(※今回はMainクラスからその他のクラスを利用していますが、パッケージを分けてprotectedを指定することで窓口からしかその他のクラスを利用できなくするということもあり得ます。

ここではFacadeを利用した場合と利用しない場合の違いが分かりやすいように、Clientからも呼び出せるようにしています。)


実行結果

Main.javaを実行した結果は以下になります。

パターンを適用した場合と未適用の場合で、処理の結果には違いがないことが確認できます。


実行結果(コンソール)

ファイルを出力しました。ファイル名:C:\work\Facade\src\outputFile1.txt

ファイルを出力しました。ファイル名:C:\work\Facade\src\outputFile2.txt


実行結果(outputFile1.txt)

***************Header***************

年齢が25歳を超えるメンバーは以下です。
 ・名前:Yamada_Hanako 年齢:30
 ・名前:Foo_Footarou 年齢:80
 ・名前:Neko_Nekota 年齢:50
***************Footer***************


実行結果(outputFile2.txt)

***************Header***************

年齢が25歳を超えるメンバーは以下です。
 ・名前:Yamada_Hanako 年齢:30
 ・名前:Foo_Footarou 年齢:80
 ・名前:Neko_Nekota 年齢:50
***************Footer***************


メリット

Facadeパターンのメリットは以下になります。

 1.呼び出し側で詳細な制御を記述する必要がないので、コードをシンプルにすることができる。

 2.意図しない呼び出しを行いバグになってしまうということを防ぐことができる。

 3.インタフェースの呼び出し口を絞ることで、外部と疎結合にすることができ、再利用性が高まる。


まとめ

複雑な処理の制御を行う窓口を提供するFacadeパターンに関して学びました。

以下でサンプルコードをアップしていますのでよろしければ参考にどうぞ。

また、他のデザインパターンに関しては以下でまとめていますので、こちらも参考にどうぞ。


参考文献