در پست قبلی، ما در مورد الگوهای طراحی Builder و Fluent Builder صحبت کردیم. بنابراین اگر با الگوی Fluent Builder آشنا نیستید، قبل از اینکه مطالعه این پست را ادامه دهید، به شدت توصیه میکنیم که پست قبلی را مطالعه کنید. در این پست، ما قصد داریم یک Fluent Builder را یک سطح بالاتر ببریم و نشان دهیم که در حالی که از Fluent Builder دیگری ارث بری میکنیم چگونه می توانیم از generic ها استفاده کنیم.
زمانیکه builder ها، از builder های دیگر ارث بری میکنند، هیچ اتفاق خاصی قرار نیست بیفتد و همه چیز باید به همان شکل باقی بماند. اما اگر یک Fluent Builder از یک Fluent Builder دیگر ارث بری کند، در اینصورت در مورد زنجیر کردن action ها به یکدیگر، به مشکل برخواهیم خورد. بنابراین قصد داریم برای فعال کردن رفتار پیشفرض اینترفیسهای fluent خود، از یک رویکرد Recursive Generics (Generic بازگشتی) استفاده کنیم.
این مقاله، بخشی از مجموعه آموزشی زیر است:
- الگوی طراحی Builder و Fluent Builder
- اینترفیس Fluent Builder به همراه Generic بازگشتی
- Facated Builder
- متد Factory
- Singleton
- Adapter
- Composite
- Decorator
- Command
- Strategy
- Facade
شما میتوانید سورس را از اینجا دانلود کنید: Fluent Builder به همراه Recursive Generics – سورس کد
برای مشاهده لیست کامل مقالات این مجموعه آموزشی، الگوهای طراحی #C را بررسی کنید.
این مقاله، به قسمتهای زیر تقسیم میشود:
مشکل با وراثت در Fluent Builder
تصور کنید که میخواهیم یک آبجکت Employee بسازیم. بنابراین بدیهی است که اولین کار، ایجاد کلاس model مان است:
1 2 3 4 5 6 7 8 9 10 11 |
public class Employee { public string Name { get; set; } public string Position { get; set; } public double Salary { get; set; } public override string ToString() { return $"Name: {Name}, Position: {Position}, Salary: {Salary}"; } } |
در ادامه میخواهیم یک کلاس builder برای ساخت قسمت Name
آبجکتمان ایجاد کنیم:
1 2 3 4 5 6 7 8 9 10 |
public class EmployeeInfoBuilder { protected Employee employee = new Employee(); public EmployeeInfoBuilder SetName(string name) { employee.Name = name; return this; } } |
حالا میتوانیم کلاس builder دیگری برای قسمت Position
ایجاد کنیم و این کلاس قرار است که از کلاس EmployeeInfoBuilder
ارث بری کند، چرا که میخواهیم از آبجکت employee خود استفاده مجدد نماییم:
1 2 3 4 5 6 7 8 |
public class EmployeePositionBuilder: EmployeeInfoBuilder { public EmployeePositionBuilder AtPosition(string position) { employee.Position = position; return this; } } |
در نهایت، میتوانیم فراخونی ها را به سمت این کلاسهای builder انجام دهیم:

اما همانطور که اینجا میبینید، ما قادر نیست آبجکت مورد نظر را ایجاد کنیم. این به این خاطر است که متد SetName
، یک نمونه از نوع EmployeeInfoBuilder
را برمیگرداند که هم اکنون متد AtPosition
راپیاده سازی نکرده و یا ارث بری نکرده است. این کاملا قابل درک است، چرا که کلاس EmployeeInfoBuilder
، یک کلاس با order بالاتر است و کلاس EmployeePositionBuilder
از آن ارث بری میکند و بالعکس آن صحیح نیست.
بنابراین، ما باید راه حلی برای انتشار اطلاعات از کلاس مشتق شده به کلاس پایه پیدا کنیم و راه حل، اینجا، رویکرد generic بازگشتی است.
پیاده سازی Generic بازگشتی با Fluent Builder
بنابراین با کلاس EmployeeBuilder
abstract شروع میکنیم که مسئول نمونه سازی و ارائه آبجکت employee میباشد:
1 2 3 4 5 6 7 8 9 10 11 |
public abstract class EmployeeBuilder { protected Employee employee; public EmployeeBuilder() { employee = new Employee(); } public Employee Build() => employee; } |
زمانیکه این کلاس را ایجاد کردیم، در ادامه میتوانیم اطلاحات را در EmployeeInfoBuilder
انجام دهیم. ما دیدم که SetName
نمیتواند نوع EmployeeInfoBuilder
را برگرداند. آن باید یک نوع generic برگرداند. با توجه به این موضوع، بیایید کلاس خود را اصلاح کنیم:
1 2 3 4 5 6 7 8 |
public class EmployeeInfoBuilder<T>: EmployeeBuilder where T: EmployeeInfoBuilder<T> { public T SetName(string name) { employee.Name = name; return (T)this; } } |
خب. حالا این یعنی چی؟
خب، آنقدرها هم که در نگاه اول به نظر میرسد پیچیده نیست.
ما گفتیم که متد SetName
، باید یکنوع generic برگرداند، بنابراین اینجا کلاس ما نیز generic میباشد. این کلاس باید از کلاس EmployeeBuilder
ارث بری کند، زیرا ما به آن آبجکت employee نیاز داریم. در آخر، ما باید مطمئن شویم که type صحیحی را برای نوع T در کلاس خود دریافت میکنیم. برای این منظور، میتوانیم نوع T را به نوع EmployeeInfoBuilder
محدود کنیم.
حالا میتوانیم کار خود را با اصلاح در EmployeePositionBuilder
ادامه دهیم:
1 2 3 4 5 6 7 8 |
public class EmployeePositionBuilder<T>: EmployeeInfoBuilder<EmployeePositionBuilder<T>> where T: EmployeePositionBuilder<T> { public T AtPosition(string position) { employee.Position = position; return (T)this; } } |
با این کار، ما وراثت را در هر دوی این کلاسها فعال ساخته ایم. آنها از رویکرد اینترفیس fluent builder پشتیبانی میکنند و حالا میتوانند نوع مورد نیاز را برگردانند.
این در سناریوی ما بسیار مفید است از آنجایی که کارمند ما به حقوق خود نیاز دارد، حالا به راحتی میتوانیم حقوق را با استفاده از متد WithSalary
در کلاس EmployeeSalaryBuilder
اضافه کنیم:
1 2 3 4 5 6 7 8 |
public class EmployeeSalaryBuilder<T>: EmployeePositionBuilder<EmployeeSalaryBuilder<T>> where T: EmployeeSalaryBuilder<T> { public T WithSalary(double salary) { employee.Salary = salary; return (T)this; } } |
در حال حاضر، ما میدانیم که چطور کلاسهای Builder را با generic بازگشتی بسازیم.
اما ما نمیتوانیم ساخت آبجکت خود را هنوز شروع کنیم.
این به این دلیل است که کاملاً مشخص نیست که هنگام ایجاد کلاس EmployeeInfoBuilder از چه نوعی باید استفاده کنیم.
بنابراین میخواهیم یک API ایجاد کنیم که امکان ساخت آبجکت را به ما بدهد:
1 2 3 4 |
public class EmployeeBuilderDirector : EmployeeSalaryBuilder<EmployeeBuilderDirector> { public static EmployeeBuilderDirector NewEmployee => new EmployeeBuilderDirector() } |
حالا میتوانیم ساخت آبجکت خود را به شیوه fluent شروع کنیم:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
class Program { static void Main(string[] args) { var emp = EmployeeBuilderDirector .NewEmployee .SetName("Maks") .AtPosition("Software Developer") .WithSalary(3500) .Build(); Console.WriteLine(emp); } } |
عالی.
حالا ما میدانیم که چطور با استفاده از یک رویکرد generic بازگشتی، وراثت اینترفیس fluent را فعال کنیم.
نتیجه گیری
در مقاله بعدی، که قرار است دوباره به الگوی Builder مربوط شود، ما در مورد Faceted Builder صحبت می کنیم و نحوه استفاده از facade برای ایجاد یک آبجکت که به بیش از یک کلاس builder نیاز دارد را به شما نشان می دهیم.