こちらが、ユースケースの最初の2項目分を確認するテストのコア部分です。
new SelfChecker(scanner, display, inventory); scannerListener.scanned(EGGS_BARCODE); assertEquals(lastDisplayed, "12345 - Green Eggs - $2.95");
このテストでは、まず、スキャナ、ディスプレイ、商品情報への照会を行うSelfCheckerオブジェクトを作成します。次に、バーコードの付いた商品(玉子)1点を擬似的にスキャンします。最後に、タッチパネルに表示された最新の内容が、玉子に関する詳細であることを確認します。SelfCheckerオブジェクトは、スキャナ、ディスプレイ、商品情報の間で必要な全てのやり取りの仲介を行います。
十分なテストを実施するには、テスト用の「代役」を立てる必要があります(リスト2を参照)。スタブ実装のScannerは、addScannerListenerに渡されるScannerListenerオブジェクトを保持しているだけです。「スパイ」オブジェクトであるTouchScreenDisplayは、appendPurchasedItemメソッドに渡される最新のString引数を取得します。Inventoryの模擬オブジェクトは、引数の正当性を確認して、コード内に記述された値を返します。
import static org.junit.Assert.*;
import java.math.*;
import org.junit.*;
public class SelfCheckerTest {
private ScannerListener scannerListener;
private String lastDisplayed;
private static final String EGGS_BARCODE = "12345";
@Test
public void singleItemSale() {
Scanner scanner = new Scanner() {
@Override
public void addScannerListener(ScannerListener listener) {
SelfCheckerTest.this.scannerListener = listener;
}
};
TouchScreenDisplay display = new TouchScreenDisplay() {
@Override
public void appendPurchasedItem(String text) {
SelfCheckerTest.this.lastDisplayed = text;
}
};
Inventory inventory = new Inventory() {
@Override
public String description(String barcode) {
assertTrue(barcode.equals(EGGS_BARCODE));
return "Green Eggs";
}
@Override
public BigDecimal price(String barcode) {
assertTrue(barcode.equals(EGGS_BARCODE));
return new BigDecimal("2.95");
}
};
new SelfChecker(scanner, display, inventory);
scannerListener.scanned(EGGS_BARCODE);
assertEquals(lastDisplayed, "12345 - Green Eggs - $2.95");
}
}
スキャナ、ディスプレイ、商品情報サブシステムの内部には、メディエータであるSelfCheckerオブジェクトが存在します(リスト3を参照)。SelfCheckerはスキャナにメッセージを送信し、自身をScannerListenerとして追加します。scannedのメッセージを受け取ると、SelfCheckerはInventoryオブジェクトにメッセージを送信することで、直ちにバーコードに対応する商品情報と価格情報を取得し、最終的にタッチパネルのディスプレイにメッセージを送信して、適切な出力文字列を渡します。
public class SelfChecker implements ScannerListener {
private final TouchScreenDisplay display;
private final Inventory inventory;
public SelfChecker(Scanner scanner, TouchScreenDisplay display,
Inventory inventory) {
this.display = display;
this.inventory = inventory;
scanner.addScannerListener(this);
}
@Override
public void scanned(String barcode) {
String purchase =
String.format("%s - %s - $%s",
barcode,
inventory.description(barcode),
inventory.price(barcode));
display.appendPurchasedItem(purchase);
}
}
Mediatorパターンを使わない別のソリューションとして、セルフレジシステムの各デバイスの「変換アダプタ」として機能するクラスを作成する、という方法も考えられます。しかし、このようなアダプタオブジェクトが互いにメッセージを送信するときの煩雑さを想像してみてください。ほとんどのアダプタクラスは他のさまざまなクラスと依存関係を持つことになり、結合度の高いシステムのあらゆるマイナス面が噴出してくるでしょう。結局は、クラス間で飛び交うメッセージのせいで、コードがより複雑となり、保守に費用がかかるものになるのではないでしょうか。

