interfaceを用いて持続可能なソフトウェア設計を目指す
TL;DR
- interfaceを使って依存関係を疎結合にする
- 変更に強いソフトウェア設計を実現する
- 実装の詳細を隠蔽することで、変更の影響範囲を最小限に抑える
記事タイトルからinterfaceのみで変更に強いソフトウェア設計を実現できるとは限りません。あくまでも一つの手法としてinterfaceに焦点を当てて執筆しています。
interfaceについて
interfaceって何?って聞かれたら、「契約」って答えるかも。
例えば、コンビニのレジ打ちのバイトを考えてみる。
interface CashierOperation {
void scanItem();
void calculateTotal();
void acceptPayment();
}
このinterfaceは「レジ打ち」という仕事の契約書みたいなもの。
- 商品をスキャンする
- 合計金額を計算する
- 支払いを受け取る
これらの操作は、誰がやっても同じ結果になるはず。新人でもベテランでも、やることは変わらない。
interfaceを意識することで何がいいのか
1. 変更が楽になる
例えば、決済システムを考えてみよう。
interface PaymentProcessor {
void processPayment(int amount);
}
class CashPayment implements PaymentProcessor {
public void processPayment(int amount) {
System.out.println("現金で" + amount + "円支払い");
}
}
class CreditCardPayment implements PaymentProcessor {
public void processPayment(int amount) {
System.out.println("クレカで" + amount + "円支払い");
}
}
新しい決済方法が追加されても、既存のコードは変更不要。PayPayとかQRコード決済とか、どんどん追加できる。
2. テストが書きやすい
モックを使ってテストが簡単に書ける。
class MockPayment implements PaymentProcessor {
public void processPayment(int amount) {
// テスト用の実装
}
}
3. 依存関係がわかりやすい
interfaceを見るだけで、そのクラスが何をするべきかがわかる。実装の詳細は隠蔽されているから、本質的な部分に集中できる。
理解度テスト
自分の理解度を確認するためのメモ。
問題1: 依存の問題
以下のコードには問題がある。
class OrderService {
private CashPayment payment = new CashPayment();
public void checkout(int amount) {
payment.processPayment(amount);
}
}
問題点
- CashPaymentに直接依存している
- 支払い方法を変更する際にOrderServiceの実装を変更する必要がある
- テストが書きづらい(モック化が難しい)
改善案
class OrderService {
private PaymentProcessor payment;
public OrderService(PaymentProcessor payment) {
this.payment = payment;
}
public void checkout(int amount) {
payment.processPayment(amount);
}
}
問題2: 実装の切り替え
新しい決済方法を追加する場合:
// PayPay決済の追加
class PayPayPayment implements PaymentProcessor {
public void processPayment(int amount) {
System.out.println("PayPayで" + amount + "円支払い");
}
}
// 使用例
OrderService service1 = new OrderService(new CashPayment());
OrderService service2 = new OrderService(new PayPayPayment());
問題3: テスト容易性
テストコードの例:
@Test
void paymentTest() {
// テスト用のモック作成
PaymentProcessor mockPayment = new PaymentProcessor() {
public void processPayment(int amount) {
// テスト用の実装
}
};
OrderService service = new OrderService(mockPayment);
service.checkout(1000);
}