本記事ではGoFのデザインパターンのプログラムの構造に関するパターンの一つである「Facade」パターンを解説します。このパターンを一言で説明するならば、「複数のクラス組み合わせて使う手順を、まとめる(窓口)クラスを作ってシンプルに利用できる方法」と言えるでしょう。「Facade」パターンを利用しない場合と、利用する場合の両方の例を挙げてわかりやすく解説します。
「Facade」パターンとは
先述した通り、「Facade」とは「複数のクラス組み合わせて使う手順を、まとめる(窓口)クラスを作ってシンプルに利用できる方法」です。「Facade」を直訳すると「建物の正面」や「外見」という意味があり、「ファサード」と読みます。
最初はシンプルな構成のプログラムだとしても、機能を追加していくとどうしても複雑な構成になりがちです。一つのサービスが他のサービスと関連しあって、より複雑なプログラムになっていきます。それらのクラスを全て利用するには、全てのクラスの仕様を詳細に理解し、正しい順序で生成して利用することが求められます。
このような場合にいろいろなサービスを一つにまとめて提供してくれるクラスがあると、利用側は簡単にさまざまなサービスの機能を利用することができます。
例として銀行の窓口を考えてみましょう。銀行の窓口ではさまざまなサービスを提供しています。口座の開設はもちろん、ローンの申請やnisaなどの投資相談等も行ってくれます。これらのサービスを受けるには、銀行の「窓口」にこれらのサービスを受けたいというだけです。
もしこのような仕組みがない場合どのようなことになるでしょうか?自分で口座を開設して、投資先を自分で見つけて、ローンの申請も別のところで行って...このように、とても手間が増えてしまいます。「facade」パターンも、このような面倒な処理を「窓口」を設けることで、利用者側が楽に実装できるようにするパターンです。
「Facade」パターンの書き方
この記事では先ほどの銀行の例で考えていきます。自身で行わないといけない作業を下記の3つとします。
- 口座の開設
- ローンの申請
- 投資先の決定
このやることリストを、銀行の窓口で全て行う方法と、自分自身ですべて探して行う方法で「Facade」パターンを開設していきます。
✖︎ Facadeパターンを使用しない例 ✖︎
まずはFacadeパターンを利用しない例です。それぞれのやることリストをサービスとしてインターフェースとし、それを実装する形で実際の処理を記述していきます。
// 口座申請サービス
interface AccountService {
void openAccount(String accountName);
}
public class AccountServiceImpl implements AccountService {
@Override
public void openAccount(String accountName) {
System.out.println(accountName + "の名義で口座を開設します。");
}
}
// ローン申請サービス
interface LoanService {
void applyForLoan(String accountName, double amount);
}
public class LoanServiceImpl implements LoanService {
@Override
public void applyForLoan(String accountName, double amount) {
System.out.println(accountName + "の名義で" + amount + "円のローンを申請します。");
}
}
// 投資先の相談サービス
interface InvestmentService {
void consultInvestment(String investor);
}
public class InvestmentServiceImpl implements InvestmentService {
@Override
public void consultInvestment(String investor) {
System.out.println(investor + "の投資先を紹介します。");
}
}
これで各やることリストの実装がかんりょうしました。これらのサービスを利用します。
public class NoFacadeMain {
public static void main(String[] args) {
// 各サービスのインスタンスを作成
AccountService accountService = new AccountServiceImpl();
LoanService loanService = new LoanServiceImpl();
InvestmentService investmentService = new InvestmentServiceImpl();
// 自分自身で各サービスを利用する
String name1 = "前田";
accountService.openAccount(name1);
loanService.applyForLoan(name1, 50000000);
investmentService.consultInvestment("nisa");
// 自分自身で各サービスを利用する
String name2 = "田中";
accountService.openAccount(name2);
loanService.applyForLoan(name2, 100000000);
investmentService.consultInvestment("ideco");
}
}
>> 実行結果
前田の名義で口座を開設します。
前田の名義で5.0E7円のローンを申請します。
nisaの投資先を紹介します。
田中の名義で口座を開設します。
田中の名義で1.0E8円のローンを申請します。
idecoの投資先を紹介します。
最初にすべてのサービスのインスタンスを作成しています。そこからサービスごとのメソッドでそれぞれの処理をしています。一見問題なく実装できていそうですが、このコードには下記のデメリットが存在します。
- 利用側がサービス側の変更をダイレクトに受けてしまう(結合度が高い)
- 同じサービスを別の場所で利用する場合、もともとの利用側に直接影響がでるため再利用が難しい
- 多くのサービスを直に呼び出しているため、該当コードのユニットテストの作成時にmockやstubを多用することになりテストが複雑になる
- 全ての関連するサービスを理解する必要があり、サービスの詳細を理解するのに時間がかかる
ここれらの理由により、窓口となるFacadeを利用することが勧められます。
⭕️ Facadeパターンを使用する例 ⭕️
それではこの問題だらけのコードをFacadeを利用して実装していきます。やることは変わらないのでそれぞれのサービスクラスに変更はありません。銀行ですべての作業ができると仮定し、窓口となるCustomerServiceFacadeクラスを実装します。
// 銀行窓口クラス(Facadeクラス)
public class CustomerServiceFacade {
private final AccountService accountService;
private final LoanService loanService;
private final InvestmentService investmentService;
// コンストラクタでそれぞれのサービスを生成
public CustomerServiceFacade() {
this.accountService = new AccountServiceImpl();
this.loanService = new LoanServiceImpl();
this.investmentService = new InvestmentServiceImpl();
}
public void openAccount(String accountName) {
accountService.openAccount(accountName);
}
public void applyForLoan(String accountName, double amount) {
loanService.applyForLoan(accountName, amount);
}
public void consultInvestment(String investor) {
investmentService.consultInvestment(investor);
}
}
このFacadeクラスではフィールドに3つのサービスを持っています。コンストラクタでCustomerServiceFacadeクラスを生成した時に全てのサービスのインスタンスが生成されます。また、窓口のクラスからそれぞれのサービスを利用できるようにメソッドを用意します。
やっていることはこれだけで、全く複雑ではありません。Facadeクラスのメソッドを挟んでサービスのメソッドを実行するといくつか利点があります。サービスのメソッドの引数や戻り値に変更が生じた場合、Facadeクラスのメソッド内で、サービスの変更を吸収できるため、利用側はコードを変更する必要がありません。これは結合度の低さを示し、コードの保守性や再利用性、テストのしやすさ等を向上させることができます。
このCustomerServiceFacadeクラスを利用すると下記のようになります。
public class FacadeMain {
public static void main(String[] args) {
// 窓口のインスタンスのみを作成
CustomerServiceFacade customerServiceFacade = new CustomerServiceFacade();
// 銀行窓口に必要な情報だけを伝える
String name1 = "前田";
customerServiceFacade.openAccount(name1);
customerServiceFacade.applyForLoan(name1, 50000000);
customerServiceFacade.consultInvestment("nisa");
// 銀行窓口に必要な情報だけを伝える
String name2 = "田中";
customerServiceFacade.openAccount(name2);
customerServiceFacade.applyForLoan(name2, 100000000);
customerServiceFacade.consultInvestment("ideco");
}
}
実行結果はFacadeを利用しない例と同一です。生成するインスタンスはCustomerServiceFacadeクラスのみで、それ以外のサービスは意識する必要がありません。極論を言えば、サービスの細かな仕様を理解していなくても、実装することは可能です。
またFacadeクラスを挟むことで、内部のロジックと外部のインターフェースを完全に分離することができ、それぞれのクラスの役割と責務が明確になります。
まとめ
本記事で利用したサンプルケースのクラス図は下記になります。
最後に「Facade」パターンとは「複数のクラス組み合わせて使う手順を、まとめる(窓口)クラスを作ってシンプルに利用できる方法」です。このパターンを利用することで、複雑なシステムを内部を詳細に理解していなくても利用できるようになり、サービス側の変更の影響を利用元で抑えることができるため、疎結合なコードで実装することが可能になります。うまく使っていきましょう。
本記事で利用したコードは下記のgit上で公開しております。
コメント