はじめに
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 }
それでは、ハッピーコーディング!