はじめに
Nullオブジェクトとは、オブジェクトが存在しないことを表すオブジェクトです。何の動作も行わずに、デフォルトを返します。Nullオブジェクトは、オブジェクト参照がNullである場合に使われます。Null Objectパターンはクライアントコードを簡素化し、誤りを生じにくくするのに役立ちます。
例えば、顧客アカウントを確認し、顧客がゴールド会員であるかどうかをチェックする簡単な販売アプリケーションを考えてみましょう。ゴールド会員は、割引や配送料無料など、さまざまな特典を受けることができるものとします。
以下は、mainメソッドのコードスニペットです。顧客のアカウント情報を取得し、そのゴールド会員プロファイルを取り出し、そのゴールド会員に適用されるすべての特典を適用します。
string userName = args[0];
string pin = args[1];
Order order = new Order();
for (int i = 2; i < args.Length; i++)
{
order.Amount += Convert.ToDecimal(args[i]);
}
var account = AccountController.GetAccount(userName, pin);
if (account == null)
{
Console.WriteLine("Invalid Account");
}
else
{
var gProfile = GoldMembershipController.GetProfile(account);
if (gProfile != null)
{
order.Amount -= gProfile.GetDiscount(order.Amount);
}
if (gProfile != null)
{
if (!gProfile.IsShippingFree)
{
order.Amount += order.GetShippingCharges();
}
}
else
{
order.Amount += order.GetShippingCharges();
}
Console.WriteLine(string.Format("Total Amount: {0}", order.Amount));
}
顧客が有効なアカウントを所有する場合、AccountController.GetAccount(userName, pin)はAccountオブジェクトを返し、それ以外の場合はnullを返します。同様に、顧客がゴールド会員であれば、GoldMembershipController.GetProfile(account)はGoldMembershipオブジェクトを返し、ゴールド会員でない場合はnullを返します。
上のコードスニペットから分かるように、null参照をチェックする条件文が何度も繰り返し出現します。条件文は通常、条件結果に基づいて分岐するために使用します。上のゴールド会員であるかをチェックする部分では、同じフローの中で、null参照をチェックするためだけに2つの条件文が使用されています。これでは、コードが長くなって理解しにくくなるだけでなく、大きなプログラムではnullのチェックを忘れてしまいがちであるため、非常に誤りが生じやすくなります。Null Objectパターンは、このような問題を解決するためのものです。
Null Objectパターンを実装すると、GoldMembershipController.GetProfile(accountId)は常にオブジェクトを返すことになります。顧客がゴールド会員ではない場合には、Nullオブジェクトを返します。Null Objectパターンを実装するには、IGoldMembershipインターフェースを作成し、GoldMembershipクラスとNullGoldMembershipクラスの両方でこのインターフェースを実装します。NullGoldMembershipクラスは、何も行わずにデフォルトを返すだけのクラスです。GoldMembershipController.GetProfile(accountId)メソッドも変更し、ユーザーがゴールド会員である場合はGoldMembershipオブジェクト、ゴールド会員でない場合はNullGoldMembershipオブジェクトを返すようにします。
public interface IGoldMembership
{
decimal GetDiscount(decimal amount);
bool IsShippingFree { get; set; }
}
public class GoldMembership : IGoldMembership
{
public GoldMembership(string userName)
{
// get gold member profile of this user
IsShippingFree = true;
}
public bool IsShippingFree { get; set; }
public decimal GetDiscount(decimal amount)
{
decimal discount = 100;
//calculate discount
return discount;
}
}
public class NullGoldMembership : IGoldMembership
{
public NullGoldMembership()
{
IsShippingFree = false;
}
public decimal GetDiscount(decimal amount)
{
return 0;
}
public bool IsShippingFree { get; set; }
}
public class GoldMembershipController
{
public static IGoldMembership GetProfile(Account account)
{
// if this account has gold membership
// return profile else return null
if (account.Type == "GoldMember")
{
return new GoldMembership(account.UserName);
}
else
{
return new NullGoldMembership();
}
}
}
Null Objectパターンを実装した後のmainメソッドのコードは、次のようになります。
string userName = args[0];
string pin = args[1];
Order order = new Order();
for (int i = 2; i < args.Length; i++)
{
order.Amount += Convert.ToDecimal(args[i]);
}
var account = AccountController.GetAccount(userName, pin);
if (account == null)
{
Console.WriteLine("Invalid Account");
}
else
{
var gProfile = GoldMembershipController.GetProfile(account);
order.Amount -= gProfile.GetDiscount(order.Amount);
if (!gProfile.IsShippingFree)
{
order.Amount += order.GetShippingCharges();
}
Console.WriteLine(string.Format("Total Amount: {0}", order.Amount));
}
Null Objectパターンを実装した後のクライアントサイドのコードは、かなりすっきりしたものになりました。簡潔であるだけでなく、理解しやすいものになっています。
しかし、Null Objectパターンを使用すればnull参照を完全になくすことができるわけではありません。オブジェクトが存在するか否かによって分岐しなければならない場合もあり、そのような場合にはやはりnull参照を使用する必要があります。例えば、ユーザーが有効なアカウントを所有しない場合は、その後の処理は行わず、単にエラーメッセージを表示します。このような場合には、次のようにnull参照を使用し、チェックする必要があります。
var account = AccountController.GetAccount(userName, pin);
if (account == null)
{
Console.WriteLine("Invalid Account");
}
else
{
// process the transaction
}
それでは、ハッピーコーディング!
