tgoop.com/fullStackDevs/445
Create:
Last Update:
Last Update:
🌀 Specification pattern: C# implementation
در این پست به معرفی الگوی Specification میپردازیم و یک مثال فوق العده کاربردی ارائه میدهیم که یک گام معماری و کدتان به Domain Driven Design و Clean Code نزدیکتر میکند.
*توضیحات تکمیلی : این پست را بهتر است با تلگرام دسکتاب و در سایز maximize مطالعه فرمایید.
🔸 یکی مزایای استفاده از این الگو را میتواند Encapsulation کردن اطلاعاتی که در مورد Domain میباشد دریک قسمت واحد بشمار آورد.
موارد استفاده از این الگو با ذکر یک مثال به طور کامل بیان شده است.
فرض کنید یکی از کلاس های دامین مدل ما کلاس Movie باشد که به صورت زیر تعریف شده است :
public class Movie : Entity
{
public string Name { get; }
public DateTime ReleaseDate{get;}
public MpaaRating MpaaRating {get;}
public string Genre { get; }
public double Rating { get; }
}
public enum MpaaRating🔹 حالا فرض کنیم که کاربر فیلم های تازه ساخته شده را میخواهد تماشا کند. برای نمایش این فیلم ها به کاربر معمولا در Repository مربوطه متدی به شکل زیر تعریف میکنیم :
{
G,
PG13,
R
}
public class MovieRepositoryیا اگر به فیلم هایی براساس Rate یا Genre یا دیگر شاخص ها نیاز داشته باشیم بدین شکل معمولا متد های ریپازیتوری مان را تعریف میکنیم :
{
public IReadOnlyList<Movie> GetByReleaseDate(DateTime minReleaseDate)
{
/* ... */
}
}
public class MovieRepository🔸 ریپازیتوری قدری پیچیده میشود اگر پارامترهای جست و جوی ما پیچیده تر شوند مثلا اگر تمامیه فیلم هایی را که در تاریخ انتشار مشخص و با ژانر خاصی هستند نیاز داشته باشید معمولا دوباره به ازای آن یک متد در ریپازیتوری مربوطه تعریف میکنید.
{
public IReadOnlyList<Movie> GetByReleaseDate(DateTime maxReleaseDate) { }
public IReadOnlyList<Movie> GetByRating(double minRating) { }
public IReadOnlyList<Movie> GetByGenre(string genre) { }
}
برای نوشتن این متد روش های زیادی وجود دارد مانند اکستنشن متد،یا یک متدی که به صورت جداگانه این فیلتر ها را بروی Dbset<Movie> اعمال کند. اما این راه حل ها راهای درستی نیستند و از جمله معیاب آن میتوان به نقض کردن اصل DRY میباشد چون بایستی به ازای هر Dbset این متد ها را که حتی ممکن است logic های یکسانی داشته باشند ، تکرار کنید.
اکنون شاید با خود میگویید خب میتوانیم از یک متد Generic برای جلوگیری از این تکرار استفاده کنیم .
راه حل این مشکل استفاده از الگوی specification است . اما با یک پیاده سازی خاص .در این پست دو پیاده سازی از این الگو ارائه خواهییم داد.
پیاده سازی اول یک پیاده سازی ساده با عنوان Naive implementation است.و در ادامه به سمت بهتر کردن ان پیش میرویم.
اولین راه حل برای مقابله با مشکل بالا (ایجاد متد های متعدد برای شاخص های متعدد در جست و جو و مرتب سازی و..) استفاده از امکان expressions در سی شارپ است تا حدی زیادی آنها خودشان هم یک پیاده سازی از الگوی specification هستند. پس میتوانید یک متد در ریپازیتوری خود بنویسیدکه به عنوان پارامتر یک expression ورودی بگیرد مثال :
// Controllerمشکلی که با این روش وجود دارد این است که با اینکه شروط مربوط به دامین مدل مان را در یک مکان واحد جمع اوری کردیم (در متغیر Expression) اما چنین متغیر هایی در این سطح (Controller) برای چنین اطلاعات مهمی اصلا مناسب نیستند.در این روش امکان دوباره استفاده از این شروط بسیار سخت میشود و حتی تمایل به duplicate در کل اپلیکیشن بشدت افزایش پیدا میکتد.و متاسفانه دوباره در نهایت به مشکل اول باز میگردیم.
public void SomeMethod()
{
Expression<Func<Movie, bool>> expression = m => m.MpaaRating == MpaaRating.G;
var movies = _repository.Find(expression); // Getting a list of movies
}
// Repository
public IReadOnlyList<Movie> Find(Expression<Func<Movie, bool>> expression)
{
return db
.Where(expression)
.ToList();
}
حال نوبت به یک پیاده سازیه ساده از الگوی Specification تحت عنوان GenericSpecification میرسد.
public class GenericSpecification<T>نحوه استفاده از آن در پست بعدی ...
{
public Expression<Func<T , bool>> Expression { get; }
public GenericSpecification(Expression<Func<T , bool>> expression)
{
Expression = expression;
}
public bool IsSatisfiedBy(T entity)
{
return Expression.Compile().Invoke(entity);
}
}
@fullStackDevs
BY Web Devs
Share with your friend now:
tgoop.com/fullStackDevs/445