الگوی Service Locator، به صورت گستردهای به عنوان یک ضد الگو شناخته میشود و هنگامیکه از این الگو استفاده میکنیم ما را با یک سری از مشکلات رو به رو میکند. ولی این الگوی طراحی به خودی خود منشاء مشکل نیست. مشکل اصلی این الگو نحوه استفاده از آن است که در این مقاله درباره آن بحث میکنیم.
مشکل اصلی الگوی Service Locator
زمانیکه یک کلاس، وابسته به یک Service Locator است، آن تمام وابستگیهای واقعی کلاس را مخفی میکند.
ما نمیتوانیم وابستگیها را با نگاه کردن به تعریف سازندهی کلاس بیان کنیم. در عوض، ما باید کلاس و شاید مشارکت کنندگانش را بخوانیم تا برای تشخیص اینکه چه کلاسهای دیگری برای کار آنها لازم است.
فرض کنید ما یک کارخانه تولید ماشین را مدل میکنیم. کارخانه، ماشینها را تولید میکند و آنها را به مکان فروش میرساند:
class Car { } class CarProducer { public void DeliverTo(int carsCount, string town) { Car[] cars = new Car[carsCount]; ... } }
class Transporter { public string Name { get; private set; } public Transporter(string name) { this.Name = name; } public void Deliver(Car[] cars, string town) { Console.WriteLine("Delivering {0} car(s) to {1} by {2}", cars.Length, town, this.Name); } }
چگونه میتوانیم تولید کننده را در این راه حل ملاقات کنیم؟ یک راه برای رسیدن به آن این است که از Service Locator استفاده کنید:
static class TransporterLocator { static IList<Transporter> transporters = new List<Transporter>(); public static void Register(Transporter transporter) { transporters.Add(transporter); } public static Transporter Locate(string name) { return transporters .Where(transporter => transporter.Name == name) .Single(); } }
class CarProducer { public void DeliverTo(int carsCount, string town) { Car[] cars = new Car[carsCount]; Transporter transporter = null; if (carsCount <= 12) transporter = TransporterLocator.Locate("truck"); else transporter = TransporterLocator.Locate("train"); transporter.Deliver(cars, town); } }
در این راه حل، تولید کننده خودرو به سادگی از مکانیزم حمل و نقل مناسبی برای روش حمل و نقل خود استفاده میکند. برای تعداد کمی از اتومبیلها، سازنده، از کامیونها استفاده میکند. در غیر این صورت، مهمترین معیار حمل و نقل، قطار است.
شناسایی مشکلات Service Locator
برای درک مشکلات راه حل قبلی، باید سعی کنیم تا از آن استفاده کنیم:
TransporterLocator.Register(new Transporter("truck")); TransporterLocator.Register(new Transporter("train")); CarProducer producer = new CarProducer(); producer.DeliverTo(7, "Tehran"); producer.DeliverTo(74, "Tehran");
TransporterLocator.Register(new Transporter("truck")); CarProducer producer = new CarProducer(); producer.DeliverTo(7, "Tehran"); producer.DeliverTo(74, "Tehran");
این قطعه از کد دارای خطاست؛ زیرا انتظار دارد قطار در Service Locator ثبت شده باشد. به صورت خلاصه همان شیء ممکن است به درستی کار کند یا با خطا رو به رو شود.
بهتر است که کلاس CarProducer را به گونهای طراحی کنید که اگر اشیای مورد نیاز آن به درستی تنظیم نشده باشند، آنگاه نتوان از آن نمونه سازی کرد.
حذف Service Locator
اگر ما ارجاعی را به یک شیء داشته باشیم، میخواهیم مطمئن باشیم که این شیء به خوبی تشکیل شده است و ما نمیخواهیم با یک سری از خطاهای اولیه که از نیازهای اولیه شیء میباشند، مواجه شویم. یکی از راهها برای حل این مشکل آن است که تمام وابستگیهای اجباری آنرا در سازنده کلاس تعریف کنیم. به این ترتیب، اگر وابستگیها در دسترس نباشند، راهی قانونی برای ساخت یک شیء وجود نخواهد داشت.
class CarProducer { private Transporter truck; private Transporter train; public CarProducer(Transporter truck, Transporter train) { if (truck == null) throw new ArgumentNullException("truck"); if (train == null) throw new ArgumentNullException("train"); this.truck = truck; this.train = train; } public void DeliverTo(int carsCount, string town) { Car[] cars = new Car[carsCount]; Transporter transporter = this.truck; if (carsCount > 12) transporter = this.train; transporter.Deliver(cars, town); } }
آیا وضعیتی وجود دارد که در آن Service Locator یک راه حل قابل قبول باشد؟
در برخی موارد بجای اینکه وابستگیها را به صورت صریح قید کنیم، بهتر است از این الگو استفاده کنیم.
این مثال را میتوان از زوایای مختلفی مورد بررسی قرار داد:
1) ما نمیتوانیم با نگاه کردن به پیاده سازی کلاس بفهمیم که چه شرایطی قبل از نمونه سازی از کلاس باید رعایت شده باشند.
2) ما نمیتوانیم بدانیم زمانیکه یک متد فراخوانی میشود، عملیات به درستی به انجام میرسد و یا با خطا رو به رو میشود.
3) ما نمیتوانیم این کلاس را در یک تست بررسی کنیم؛ زیرا آن کلاس وابسته به اشیاء مبهمی هست که در جای دیگری تنظیم شدهاند.
همه این مسائل جدی هستند. با این دلایل است که Service Locator به عنوان یک ضد الگو در نظر گرفته شده است. اما ... این ضد الگوی در کدها شیء گرا است. اما تمام کدهای ما شیء گرا نیستند.
زمانیکه ما از یک پایگاه داده رابطهای در حال استفاده هستیم، منطق Persistence از حالت شیء گرایی خود خارج میشود. منطق Persistence به صورت عمدهای برای نگاشت مدلهای داده به جداول است. منطق رابط کاربری ( User Interface ) نیز شیء گرا نیست؛ زیرا عمدتا از نگاشت بین داده ساده و عناصر رابط کاربر تشکیل شدهاست.
در نتیجه، عنصر مشترک در هر دو مورد، نگاشت است و این دقیقا همان چیزی است که Service Locator انجام میدهد؛ نگاشت کلیدها به اشیاء. پس چرا ما نباید از Service Locator در لایههایی که عمدتا شیء گرا نیستند استفاده کنیم؟
نتیجه گیری
در این مقاله ما به الگویی پرداختیم که در عمل به صورت گستردهای از آن اجتناب میشود. مشکل Service Locator این است که اصول طراحی شیء گرا را نقض میکند. اما در عین حال، مناطقی از کد وجود دارند که طبیعت آنها شیء گرا نیستند. لایههای Presentation و persistence شیء گرا نیستند. در عوض، آنها در حال نگاشت مدل به چیزهای دیگری، جداول و ستون در پایگاه داده و یا عناصر رابط کاربری هستند. اینها مکان هایی هستند که الگوی طراحی Service Locator را میتوان با خیال راحت و بدون نقض هر یک از دستورالعملهای شیء گرایی، صرفا به این دلیل که این مکانها به هیچ وجه شیء گرا نیستند، استفاده کرد.