تزریق وابستگی در Class Libraries
پنجشنبه 17 دی 1394در این مقاله قصد داریم چگونگی استفاده از (اصل وارونگی کنترل) inversion of control principleهنگام ایجاد و نشر کلاسها برای دیگر توسعه دهندگان را توضیح دهیم.
در این مقاله قصد داریم چگونگی استفاده از (اصل وارونگی کنترل) inversion of control principle هنگام ایجاد و نشر کلاسها برای دیگر توسعه دهندگان را توضیح دهیم.
هنگام طراحی فریم ورک خود همیشه می خواهید اینترفیس یا رابط های مستقل و کلاس هایی برای مشتری حاضر کنید. به طور مثال یک کلاس با نام ModelService دارید که یک کلاس ساده برای دسترسی به داده ها است که از یک فریم ورک با نام SimpleORM منتشر شده است. هنگامی که مسئولیت ها را تقسیم میکنید و رابط های خود را تفکیک میکنید ، طراحی خود را جایی که کلاس ModelService از اینترفیسهای دیگر با نام های IConnectionStringFactory ، IDTOMapper ، IValidationService استفاده میکند توسعه می دهید.
تزریق اینترفیس های وابسته به کلاس ModelService به طوری که بتوان آن را تست کرد ، براحتی از طریق Constructor injection تزریق سازنده بدست می آید.
public ModelService(IConnectionStringFactory factory, IDTOMapper mapper, IValidationService validationService) { this.factory = factory; this.mapper = mapper; this.validationService = validationService; }
این نوع از تزریق وابستگی ها غالبا زمانی استفاده می شود که ماژول ها خود را در برنامه گرد آورید و نمی خواهید آنها را به عنوان یک کتابخانه مستقل نشر دهید. برای نمونه ای از کلاس ModelService تزریق وابستگی ها یا DI container را جستجو خواهیم کرد . در اینجا باید از IConnectionStringFactory ، IDTOMapper و IvalidationService یا هر اتصال دیگری مطلع باشیم.
از طرفی دیگر هنگامی که کلاس های خود را برای استفاده دیگران منتشر میکنید ، نمی خواهید قادر به تزریق هر رابطی در کلاس شما باشند. همچنین نمی خواهید آنها نگران پیاده سازی هر اینترفیسی که برای پاس دادن به سازنده نیاز دارند باشند. هر چیزی بجز کلاس ModelService باید پنهان شود. مشتری می تواند از کلاس ModelService توسط دستور زیر نمونه ای ایجاد کند.
var modelService = new ModelService();
کد بالا زمانی که به یک فراخوانی کننده اجازه تغییر دادن متد کلاس خود را بدهید ، اجرا نخواهد شد. در این موقعیت، استراتژی یا الگوی طراحی ای را پیاده سازی میکنید که به طور مشخص ، سازنده را با کمی تفاوت تعریف کند.
ساده ترین راه برای دستیابی به قابلیت تست و ترک سازنده بدون پارامتر برای فراخواننده فریم ورک به صورت زیر می باشد:
public ModelService() : this(new ConnectionStringFactory(), new DTOMapper(), new ValidationService() { // no op } internal ModelService(IConnectionStringFactory factory, IDTOMapper mapper, IValidationService validationService) { this.factory = factory; this.mapper = mapper; this.validationService = validationService; }
با فرض اینکه کلاس ModelService خود را در پروژه تست جداگانه ای تست میکنید، بخاطر داشته باشید که صفت InternalVisibleTo را درون فایل ویژگی های SimpleORM تنظیم کنید.
[assembly: InternalsVisibleTo("SimpleORM.Test")]
استفاده از این روش مزیت هایی به همراه دارد : پنهان کردن سازنده به همراه پارامترهایش از فریم ورک کاربر و اجازه وارد کردن داده های نامعتبر برای تست .
[TestInitialize] public void SetUp() { var factory = new Mock<IConnectionStringFactory>(); var dtoMapper = new Mock<IDTOMapper>(); var validationService = new Mock<ivalidationservice>(); modelService = new ModelService(factory.Object, dtoMapper.Object, validationService.Object); }
روش بالا یک مشکل واضح دارد : کلاس ModelService شما یک وابستگی مستقیم بر کلاسهای مرکب ConnectionStringFactory , DTOMapper و ValidationService دارد . این نقض loose coupling یا اصل اتصال سست ، کلاس ModelService را به صورت استاتیک وابسته بر اجرای سرویسها ایجاد میکند. برای دور شدن از این وابستگی ها اضافه کردن یک ServiceLocator که مسئول نمونه شیء خواهد بود پیشنهاد می شود :
internal interface IServiceLocator { T Get<T>(); } internal class ServiceLocator { private static IServiceLocator serviceLocator; static ServiceLocator() { serviceLocator = new DefaultServiceLocator(); } public static IServiceLocator Current { get { return serviceLocator; } } private class DefaultServiceLocator : IServiceLocator { private readonly IKernel kernel; // Ninject kernel public DefaultServiceLocator() { kernel = new StandardKernel(); } public T Get<T>() { return kernel.Get<T>(); } } }
یک کلاس ServiceLocator معمولی که از Ninject به عنوان چهارچوب تزریق وابستگی استفاده میکند نوشتیم. هر فریم ورکی که برای تزریق وابستگی بخواهید می توانید استفاده کنید. توجه داشته باشید که کلاس ServiceLocator و رابط مربوط به آن داخلی است. اکنون فراخوانی مقداردهی اولیه مستقیم را برای کلاس های وابسته شده با فراخوانی ServiceLocator جابجا میکنیم.
public ModelService() : this( ServiceLocator.Current.Get<IConnectionStringFactory>(), ServiceLocator.Current.Get<IDTOMapper>(), ServiceLocator.Current.Get<IValidationService>()) { // no op }
باید برای IConnectionStringFactory و IDTOMapper و IValidationService اتصالات پیش فرضی را تعریف کنید.
internal class ServiceLocator { private static IServiceLocator serviceLocator; static ServiceLocator() { serviceLocator = new DefaultServiceLocator(); } public static IServiceLocator Current { get { return serviceLocator; } } private sealed class DefaultServiceLocator : IServiceLocator { private readonly IKernel kernel; // Ninject kernel public DefaultServiceLocator() { kernel = new StandardKernel(); LoadBindings(); } public T Get<T>() { return kernel.Get<T>(); } private void LoadBindings() { kernel.Bind<IConnectionStringFactory>().To<ConnectionStringFactory>().InSingletonScope(); kernel.Bind<IDTOMapper>().To<DTOMapper>().InSingletonScope(); kernel.Bind<IValidationService>().To<ValidationService>().InSingletonScope(); } } }
اشتراک گذاری وابستگی ها در میان کلاس های کتابخانه مختلف
همانطور که همچنان در حال توسعه چهارچوب SimpleORM خود هستید ، تقسیم کتابخانه به زیر ماژول های مختلف به پایان خواهد رسید. فرض کنید می خواهید برای یک کلاس، گسترشی را فراهم کنید که یک تعامل را با دیتابیس NoSQL پیاده سازی میکند . شما نمیخواهید که چهارچوب SimpleORM شما با وابستگی های غیر ضروری بهم ریخته شود ، بنابراین ماژول SimpleORM.NoSQL را جداگانه ایجاد میکنیم. دسترسی به تزریق وابستگی چگونه می شود؟ چگونه می توان اتصالات بیشتری را به هسته Ninject اضافه کرد؟
در زیر راه حلی ساده برای آن نوشته ایم. اینترفیسی با نام IModuleLoader را در کتابخانه کلاس اولیه SimpleORM تعریف میکنیم.
public interface IModuleLoader { void LoadAssemblyBindings(IKernel kernel); }
بجای اتصال مستقیم اینترفیس به پیاده سازی واقعی آن در کلاس SrviceLocator ، کلاس IModuleLoader را پیاده سازی و اتصالا ت را فراخوانی میکنیم.
internal class SimpleORMModuleLoader : IModuleLoader { void LoadAssemblyBindings(IKernel kernel) { kernel.Bind<IConnectionStringFactory>().To<ConnectionStringFactory>().InSingletonScope(); kernel.Bind<IDTOMapper>().To<DTOMapper>().InSingletonScope(); kernel.Bind<IValidationService>().To<ValidationService>().InSingletonScope(); } }
اکنون LoadAssemblyBindings از کلاس Service Locator فراخوانی شد. نمونه ساخته شده از این کلاس ها ، پاسخی به فراخوانی می باشد.
internal class ServiceLocator { private static IServiceLocator serviceLocator; static ServiceLocator() { serviceLocator = new DefaultServiceLocator(); } public static IServiceLocator Current { get { return serviceLocator; } } private sealed class DefaultServiceLocator : IServiceLocator { private readonly IKernel kernel; // Ninject kernel public DefaultServiceLocator() { kernel = new StandardKernel(); LoadAllAssemblyBindings(); } public T Get<T>() { return kernel.Get<T>(); } private void LoadAllAssemblyBindings() { const string MainAssemblyName = "SimpleORM"; var loadedAssemblies = AppDomain.CurrentDomain .GetAssemblies() .Where(assembly => assembly.FullName.Contains(MainAssemblyName)); foreach (var loadedAssembly in loadedAssemblies) { var moduleLoaders = GetModuleLoaders(loadedAssembly); foreach (var moduleLoader in moduleLoaders) { moduleLoader.LoadAssemblyBindings(kernel); } } } private IEnumerable<IModuleLoader> GetModuleLoaders(Assembly loadedAssembly) { var moduleLoaders = from type in loadedAssembly.GetTypes() where type.GetInterfaces().Contains(typeof(IModuleLoader)) type.GetConstructor(Type.EmptyTypes) != null select Activator.CreateInstance(type) as IModuleLoader; return moduleLoaders; } }
این کد همه assembly های بارگذاری شده در AppDomain برای پیاده سازی IModuleLoader جستجو میکند. و هنگامی که آن را یافت کرد ، هسته Singleton شما را به نمونه خود میفرستد، از استفاده شدن container مشابه در ماژول ها اطمینان یابید.
فریم ورک SimpleORM.NoSQL شما کلاس IModuleLoader خود را اجرا میکند که با اولین فراخوانی کلاس ServiceLocator فراخوانی و نمونه سازی خواهد شد. کد بالا بر وابستگی SimpleORM.NoSQLبه SimpleORM دلالت دارد که ماژول های گسترش یافته را به والد های خود وابسته میکند.
روش توضیح داده اشکالاتی دارد : مدیریت منابع یکبار مصرف ، rebinding تصادفی در ماژول های وابسته ، سربار عملکرددر ایجاد و ... که باید با احتیاط با یک مجموعه تست استفاده شوند.
- C#.net
- 1k بازدید
- 1 تشکر