در این مقاله حالتهایی را که الگوی طراحی Null Object، قادر به تشخیص آنها نیست را به وسیله الگوی طراحی Special case رفع میکنیم.
گاهی از اوقات پیش خواهد آمد که ما تسلیم شده و شیءای را برمیگردانیم که خودش نشان دهنده خطاست. اما همه شکستها یکسان نیستند. در ابتدا، کاربر ممکن است واجد شرایط انجام خرید نباشد. اما اگر واجد شرایط باشد، آیتم مورد نظر ممکن است در انبار نباشد. در صورت موجود بودن در انبار، حساب کاربر ممکن است مبلغ کافی نداشته باشد.
بجای برگشت دادن شیء Null در تمام این موارد، ما میتوانیم نتیجه را اصلاح کنیم و اساسا هر بار یک شیء مختلف را بازگردانیم. اینها هنوز هم نوعی از اشیاء Null هستند؛ ولی اینبار دارای معانی هستند. یکی از انها برای «حساب کاربری ناکافی» است، یکی دیگر برای «سایت در دست تعمیر و نگهداری» است و یا یکی دیگر از آنها «موجود نبودن در انبار» خواهد بود.
چنین اشیائی به موارد خاص ( Special Case ) اشاره میکنند. ما میتوانیم اشیاء مورد خاص ( Special Case ) را به عنوان نتایج واقعی عملیات بسازیم. فقط اگر تمام بررسیهای کسب و کار به پایان برسند و عملیات موفقیت آمیز باشد، آنگاه شیء واقعی نتیجه را باز میگردانیم.
نمونهای از پیاده سازی موارد خاص
این بخشی از رابط کاربری است که توسط سرویسهای برنامه کاربردی اجرا میشود:
public interface IApplicationServices { ... IReceiptViewModel LoggedInUserPurchase(string itemName); }
لایه نمایش انتظار دارد که لایه نرم افزار یک ویو مدل به خصوصی را برای آن تولید کند. در حال حاضر ما یک سناریوی موفقیت آمیز را داریم که در آن ویو مدل شامل اطلاعات واقعی از خرید است و چندین سناریوی شکست.
اگرعملیات خرید با هر کدام از شرایط زیر مواجه شود به شکست میانجامد:
1) سایت در دست تعمیر و نگهداری باشد.
2) کاربر ثبت نشده و یا فعال نیست.
3) آیتمی موجود نیست و یا وجود ندارد.
4) موجودی کاربر کم باشد.
public class DownForMaintenance: IReceiptViewModel { }
public class InvalidUser: IReceiptViewModel { public string UserName { get; private set; } public InvalidUser(string userName) { this.UserName = userName; } }
public class OutOfStock: IReceiptViewModel { public string UserName { get; private set; } public string ItemName { get; private set; } public OutOfStock(string userName, string itemName) { this.UserName = userName; this.ItemName = itemName; } }
public class InsufficientFunds: IReceiptViewModel { public string UserName { get; private set; } public decimal Amount { get; private set; } public string ItemName { get; private set; } public InsufficientFunds(string userName, decimal amount, string itemName) { this.UserName = userName; this.Amount = amount; this.ItemName = itemName; } }
public class ApplicationServices: IApplicationServices { ... public IReceiptViewModel LoggedInUserPurchase(string itemName) { if (IsDownForMaintenance()) return new DownForMaintenance(); return this.domain.Purchase(Session.LoggedInUserName, itemName); } private bool IsDownForMaintenance() { return File.Exists("maintenance.lock"); } }
public class DomainServices: IDomainServices { public IReceiptViewModel Purchase(string userName, string itemName) { User user = this.userRepository.Find(userName); if (user == null) return new InvalidUser(userName); Account account = this.accountRepository.FindByUser(user); return this.Purchase(user, account, itemName); } private IReceiptViewModel Purchase(User user, Account account, string itemName) { Product item = this.productRepository.Find(itemName); if (item == null) return new OutOfStock(user.UserName, itemName); ReceiptDto receipt = user.Purchase(item); MoneyTransaction transaction = account.Withdraw(receipt.Price); if (transaction == null) return new InsufficientFunds(user.UserName, receipt.Price, itemName); return receipt; } }