その記事で説明されている概念は、バックグラウンドでサービスロケーターを使用するアンビエントコンテキストです。
静的プロパティとServiceLocatorを使用しているため、このパターンは単体テストには非常に不便です。このシングルトンを使用するコードを検証するテストを実行できるようにするには、有効なService Locatorをセットアップし、テストの使用に関心のあるシングルトン(おそらくモックインスタンス)で構成する必要があります。
「シングルトンは好きですか?」という理由で、記事に示されている例でさえ、すでにこれらの問題に悩まされています。コード、テストするのは難しい:
if (DialogDisplayer.getDefault().yesOrNo(
"Do you like singletons?"
)) {
System.err.println("OK, thank you!");
} else {
System.err.println(
"Visit http://singletons.apidesign.org to"
+ " change your mind!"
);
}
より良い代替策は、コンストラクターインジェクションを使用してそのシングルトンをインジェクトすることです(私のフランス語を失礼しますが、私はネイティブJavaスピーカーではありません):
public class AskTheUserController
{
private DialogDisplayer dialogDisplayer;
private MessageDisplayer messageDisplayer;
public AskTheUserController(DialogDisplayer dialogDisplayer,
MessageDisplayer messageDisplayer)
{
this.dialogDisplayer = dialogDisplayer;
this.messageDisplayer = messageDisplayer;
}
public void AskTheUser()
{
if (this.dialogDisplayer.yesOrNo(
"Do you like singletons?"
)) {
this.messageDisplayer.display("OK, thank you!");
} else {
this.messageDisplayer.display(
"Visit http://singletons.apidesign.org to"
+ " change your mind!"
);
}
}
}
そのコードには別の「隠された」依存関係がありました:System.err.println
。MessageDisplayer
インターフェイスを使用して抽象化されました。このコードには、いくつかの明らかな利点があります。
- 両方の依存関係を注入することにより、コンシューマーはそれらの依存関係がシングルトンであることを知る必要さえありません。
- コードは、必要な依存関係を明確に伝えます。
- コードは、モックオブジェクトを使用して簡単にテストできます。
- テストコードは、サービスロケーターを構成する必要はありません。
テストは次のようになります。
@Test
public void AskTheUser_WhenUserSaysYes_WeThankHim()
{
// Arrange
bool answer = true;
MockMessageDisplayer message = new MockMessageDisplayer();
MockDialogDisplayer dialog = new MockDialogDisplayer(answer);
AskTheUserController controller =
new AskTheUserController(dialog, message);
// Act
controller.AskTheUser();
// Assert
Assert.AreEqual("OK, thank you!", message.displayedMessage);
}
@Test
public void AskTheUser_WhenUserSaysNo_WeLetHimChangeHisMind()
{
// Arrange
bool answer = true;
MockMessageDisplayer message = new MockMessageDisplayer();
MockDialogDisplayer dialog = new MockDialogDisplayer(answer);
AskTheUserController controller =
new AskTheUserController(dialog, message);
// Act
controller.AskTheUser();
// Assert
Assert.IsTrue(
message.displayedMessage.contains("change your mind"));
}
記事に示されているように、「注入可能なシングルトン」パターンを使用している場合、テストコードは上記のコードほど明らかになることはありません。