decorator design Pattern

مقدمه : الگو های طراحی راه حل هایی برای حل مشکلات نرم افزاری می باشند که توسط برنامه نویس های دیگر پیاده سازی شده اند.برای اطلاعات بیشتر درباره الگو های طراحی مقالات tutorialspoint را میتوانید به صورت جزی تر مطالعه کنید.در این سری از مقالات سعی داریم به صورت ساده و مفهومی همراه با مثال الگو های طراحی رو تشریح کنیم.خوشحال میشم نظرتون رو در قسمت کامنت ها بگید.همچنین لینک پروژه را در این ریپازیتوری میتوانید مشاهده کنید.در هر مقاله یک الگو طراحی را مورد بررسی قرار می دهیم و وارد پیش نیاز ها نمیشویم فرض کنید از یک سرویس برای ارسال ایمیل به یک کاربر خاص استفاده میکنید ورژن کتاب خانه ای که استفاده میکنید تنها قادر به ارسال ایمیل به یک گیرنده است از شما خواسته میشود چند سرویس دیگر تحت عنوان ارسال به کل کاربران و ارسال به چند کاربر رو پیاده سازی کنید قصد نداریم کلاسی را ویرایش کنیم یا یک کلاس جدید برای سرویس های جدید ایجاد کنیم یا از inheritance برای پیاده سازی استفاده کنیم. ؛ Decorator : الگو Decorator که به عنوان یک Wrapper هم شناخته میشود به برنامه نویس اجازه می دهد که به صورت دینامیکی یک رفتار یا رویداد را به یک کلاس خارجی بدون هیچ تغییری اضافه کند اگر با SOLID آشنایی داشته باشید با این کار اصل open-closed principle به همین موضوع اشاره دارد . Decorator به شما این امکان را می دهد که بخش business logic را درون لایه ها(wrappers) پیاده سازی کنید در این صورت هر لایه بخشی از business logic سیستم رو بر عهده خواهد داشت که به Single Responsibility Principle اشاره دارد.به عنوان مثال در دنیای واقعی سفارش پیتزا را در نظر بگیرید .هنگام سفارش پیتزا می توانیم بخواهیم که با روکش های مختلفی تزیین(decorated) شود . می توانیم رویه پنیر ، رویه پپرونی یا ترکیبی از بسیاری از مواد اضافه دیگر را درخواست کنیم.شروع به کد نویسی : برای درک بهتر یک پروژه ساده console را پیاده سازی خواهیم کرد سورس کد پروژه را میتوانید از این لینک دانلود کنید .یک مدل ساده به نام players به فیلد name و id ایجاد خواهیم کرد.public class Player { public int Id { get; set; } public string Name { get; set; } }در ادامه یک سرویس به نام PlayerService ایجاد خواهیم کرد که یک مدل فیک شده از player رو بازگشت می دهد .چون از DI استفاده میک نیم interfase سرویس PlayerService به شکل زیر پیاده سازی می کنیمpublic interface IPlayersService { IEnumerable<Player> GetPlayersList(); }public class PlayersService : IPlayersService { public IEnumerable<Player> GetPlayersList() { return new List<Player>() { new Player(){ Id = 1, Name = &quotJuan Mata&quot }, new Player(){ Id = 2, Name = &quotPaul Pogba&quot }, new Player(){ Id = 3, Name = &quotPhil Jones&quot }, new Player(){ Id = 4, Name = &quotDavid de Gea&quot }, new Player(){ Id = 5, Name = &quotMarcus Rashford&quot } }; } }سپس به program.cs باز میگردیم و به صورت زیر سرویس را به کلاس تزریق کرده و مقدار بازگشتی از سرویس را چاپ می کنیم private static void CallWhithOutDecorator() { var collection = new ServiceCollection(); collection.AddScoped<IPlayersService, PlayersService>(); IServiceProvider serviceProvider = collection.BuildServiceProvider(); var _playersService = serviceProvider.GetService<IPlayersService>(); var players = _playersService.GetPlayersList(); foreach (var player in players) { Console.WriteLine($&quot{player.Id} {player.Name}&quot); } }میتوان گفت ساده ترین حالت فراخوانی سرویس را با DI پیاده سازی کردیم خروجی طبق چیزی که درون سرویس بازگشت داده شده چاپ میشود .پیاده سازی الگو Decorator :قصد داریم یک decorator تحت عنوان logging decorator بر روی PlayerService پیاده سازی کنیم .logging decorator اجازه می دهد پیام های خود رو log کنیم در این مثال یک لاگ مبنی بر شروع و پایان فراخوانی سرویس خواهیم گذاشت.یک کلاس جدید به اسم PlayersServiceLoggingDecorator ساخته و از IPlayerService ارث بری می کنیم متد GetPlayersList را درون PlayersServiceLoggingDecorator پیاده سازی می کنیم.درون متد GetPlayersList مربوط به کلاس PlayersServiceLoggingDecorator متد مربوط به PlayerService را فراخوانی می کنیم با این تفاوت که قبل و بعد فراخوانی log های لازم را ثبت می کنیم همچنین مدت زمان اجرا سرویس را هم با Stopwath ثبت می کنیم.در این مثال منظور از لاگ گرفتن چاپ بر روی صفحه نمایش است و هیچ جای دیگری ذخیره سازی انجام نمیشود.public class PlayersServiceLoggingDecorator : IPlayersService { private readonly IPlayersService _playersService; private readonly ILogger<PlayersServiceLoggingDecorator> _logger; public PlayersServiceLoggingDecorator(IPlayersService playersService, ILogger<PlayersServiceLoggingDecorator> logger) { _playersService = playersService; _logger = logger; } public IEnumerable<Player> GetPlayersList() { _logger.LogInformation(&quotStarting to fetch data&quot); var stopwatch = Stopwath.StartNew(); IEnumerable<Player> players = _playersService.GetPlayersList(); foreach (var player in players) { _logger.LogInformation(&quotPlayer: &quot + player.Id + &quot, Name: &quot + player.Name); } stopwatch.Stop(); var elapsedTime = stopwatch.ElapsedMilliseconds; _logger.LogInformation($&quotFinished fetching data in {elapsedTime} milliseconds&quot); return players; } }با استفاد از dependency injection سرویس IPlayersService را به PlayersServiceLoggingDecorator تزریق می کنیم .چون از IPlayersService ارث بری کرده ایم متد GetPlayersList را باید پیاده سازی کنیم.درون GetPlayersList متد GetPlayersList مربوط به _playersService را که داده ها برگشت می دهد را فراخوانی می کنیم قبل و بعد از فرواخوانی با استفاده از console.writeline لاگ میندازیم.به program.cs بازگشته و به صورت کانفیگ کنید var collection = new ServiceCollection(); collection.AddScoped<IPlayersService, PlayersService>(); IServiceProvider serviceProvider = collection.BuildServiceProvider(); var _playersService = serviceProvider.GetService<IPlayersService>(); IPlayersService _playersServiceDecorator = new layersServiceLoggingDecorator(_playersService); var players = _playersServiceDecorator.GetPlayersList(); foreach (var player in players) { Console.WriteLine($&quot{player.Id} {player.Name}&quot); }علاوه بر تنظیمات حال ساده در خط 5 کلا دکوریتور را به سرویس نسبت داده ایم.وقتی پروژه را اجرا کنید متد GetPlayersList درون دکوریتور اجرا میشود که خود GetPlayersList متد کلاس پدر خودش را فراخوانی میکند.قبل و بعد از فراخوانی متد کلاس پدر لاگ های مورد نظر را ثبت می کنیم.دقت کنید در پروژه های mvc یا api کانفیگ کردن سرویس ها کمی متفاوت انجام میشود برای کانفیگ کردن سرویس ها به لینک منابعی که در اخر مقاله گذاشتم میتوانید کمک بگیرید.دقت کنید ما از کتاب خانه ای برای کش و لاگ کردن استفاده نکردیم و به صورت سودوکو کدارو پیاده سازی کردیمپیاده سازی Caching Decorator در مثال قبل Decorator مربوط به لاگ رو پیاده سازی کردیم همانطور که گفتیم میتوانیم Decorator ها مختلفی داشته باشم قصد داریم Decorator مربوط به Caching را پیاده سازی کنیم شبیه مثال قبل یک کلاس به اسم PlayersServiceCachingDecorator ایجاد کنیدpublic class PlayersServiceCachingDecorator : IPlayersService { private readonly IPlayersService _playersService; private readonly IMemoryCache _memoryCache; private const string GET_PLAYERS_LIST_CACHE_KEY = &quotplayers.list&quot public PlayersServiceCachingDecorator(IPlayersService playersService, IMemoryCache memoryCache) { _playersService = playersService; _memoryCache = memoryCache; } public IEnumerable<Player> GetPlayersList() { IEnumerable<Player> players = null; // Look for the cache key. if (!_memoryCache.TryGetValue(GET_PLAYERS_LIST_CACHE_KEY, out players)) { // Cache key is not in cache, so fetch players list. players = _playersService.GetPlayersList(); // Set cache options var cacheEntryOptions = new MemoryCacheEntryOptions() // Keep the players in cache for this time, reset time if accessed. .SetSlidingExpiration(TimeSpan.FromMinutes(1)); // Save players list in cache. _memoryCache.Set(GET_PLAYERS_LIST_CACHE_KEY, players, cacheEntryOptions); } return players; } }شبیه مثال قبل از IPlayersService ارثبری می کنیم قبل و بعد از فراخوانی GetPlayersList چک می کنیم آیا داده در کش قرار دارد یا خیر بسته به شرطی که گذاشته ایم داده رو بازگشت میدهیم .باید شبیه مثال قبل تنظیمات مربوط به Di را انجام داده و سرویس decorator را تزریق کنیم.اینبار برای رجستر کردن سرویس ها از کتاب خانه Scrutor استفاده خواهیم کرد .قبل از شروع Scrutor را از nuget نصب کنید Install-Package Scrutor -Version 3.3.0و به صورت زیر سرویس PlayersServiceCachingDecorator را رجستر کنید services.Decorate<IPlayersService, PlayersServiceLoggingDecorator>();به program.cs بازگشته و به صورت زیر کد را ریفکتور کنید.درون ریپازیتوری کد های قبلی را کامنت کردیم var collection = new ServiceCollection(); collection.AddScoped<IPlayersService, PlayersService>(); collection.Decorate<IPlayersService, PlayersServiceCachingDecorator>(); collection.Decorate<IPlayersService, PlayersServiceLoggingDecorator>(); IServiceProvider serviceProvider = collection.BuildServiceProvider(); var _playersService = serviceProvider.GetService<IPlayersService>(); var players = _playersService.GetPlayersList(); foreach (var player in players) { Console.WriteLine($&quot{player.Id} {player.Name}&quot); }کانفیگ بالا ساده تر از کانفیگ توکار خود dotnet است همچنین میتوانید یکسری تنظیمات پیشرفته را هنگام inject کردن اضافه کنید .پروژه را اجرا کنید باید خروجی مشابه زیر مشاهده کنیداگر دقت کرده باشید با یک بار فراخوانی GetPlayersList هم سرویس مربوط به Log و Caching فراخوانی میشوند.با تشکر از شرکت آسا برای وقتی که برای تولید این مقاله بهم دادن.اگر مقاله براتون مفید واقع شده لطفا تیک ستاره گیتابم رو به ادرس زیر بزنید. https://github.com/Sobi1995 اگه از مقاله راضی بودید میتونید از لینک زیر برام قهوه بخرید : ) http://www.coffeete.ir/Sobhan منابعhttps://www.ezzylearning.net https://www.programmingwithwolfgang.comhttps://code-maze.com