آموزش ساخت Fluent API در سی شارپ
در این مقاله چگونگی ساخت Fluent API (با نام Fluent Interface نیز شناخته می شود) در زبان برنامه نویسی سی شارپ را بررسی کرده ایم. توجه داشته باشید که در این آموزش از نسخه 5 فریم ورک .NET و نرمافزار ویژوال استودیو 2019 استفاده شده است.
فهرست مطالب
- Fluent API چیست؟
- ایجاد یک نمونه به صورت عادی
- ایجاد یک نمونه به صورت API Fluent
- محدود سازی انتخاب در Fluent API
Fluent API چیست؟
Fluent API در واقع یک روش طراحی API است که در آن از Method Chaining استفاده می شود و کدهای نوشته شده به این صورت خوانایی بالایی دارند. در فریم ورک مشهور Entity نیز از این روش برای طراحی API استفاده شده است. برای مثال:
1 2 3 4 5 | modelBuilder.Entity<Student>() .Property(s => s.StudentId) .HasColumnName("Id") .HasDefaultValue(0) .IsRequired(); |
برای درک بهتر در ادامه یک مثال را هم به صورت عادی و هم به صورت Fluent پیادهسازی خواهیم کرد.
ایجاد یک نمونه به صورت عادی
برای نمونه ما یک کلاس ایجاد خواهیم کرد که کار آن ساخت رشته اتصال به دیتابیس است. ابتدا یک کلاس با نام ConnectionStringBuilder ایجاد کرده و محتوای آن را مانند نمونه زیر تغییر دهید:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | public class ConnectionStringBuilder { private string _server; private string _database; private string _username; private string _password; public void SetServer(string server) => _server = server; public void SetDatabase(string database) => _database = database; public void SetUsername(string username) => _username = username; public void SetPassword(string password) => _password = password; public string Build() => $"Server={_server};Database={_database};User Id={_username};Password={_password};"; } |
حال می توانیم از کلاس فوق به صورت زیر استفاده کنیم:
1 2 3 4 5 6 7 8 9 | var connectionStringBuilder = new ConnectionStringBuilder(); connectionStringBuilder.SetServer("127.0.0.1"); connectionStringBuilder.SetDatabase("AwesomeCSharpAppDb"); connectionStringBuilder.SetUsername("db_user"); connectionStringBuilder.SetPassword("db_user_password"); var connectionString = connectionStringBuilder.Build(); Console.WriteLine(connectionString); // Output: Server=127.0.0.1;Database=AwesomeCSharpAppDb;User Id=db_user;Password=db_user_password; |
کد کامل مثال فوق به این صورت خواهد بود:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | using System; public class ConnectionStringBuilder { private string _server; private string _database; private string _username; private string _password; public void SetServer(string server) => _server = server; public void SetDatabase(string database) => _database = database; public void SetUsername(string username) => _username = username; public void SetPassword(string password) => _password = password; public string Build() => $"Server={_server};Database={_database};User Id={_username};Password={_password};"; } internal class Program { internal static void Main() { var connectionStringBuilder = new ConnectionStringBuilder(); connectionStringBuilder.SetServer("127.0.0.1"); connectionStringBuilder.SetDatabase("AwesomeCSharpAppDb"); connectionStringBuilder.SetUsername("db_user"); connectionStringBuilder.SetPassword("db_user_password"); var connectionString = connectionStringBuilder.Build(); Console.WriteLine(connectionString); // Output: Server=127.0.0.1;Database=AwesomeCSharpAppDb;User Id=db_user;Password=db_user_password; } } |
ایجاد یک نمونه به صورت Fluent API
در این بخش مثال فوق را به Fluent تبدیل خواهیم کرد. در این نوع طراحی خروجی متدها از نوع خود کلاس است به این معنی که هر متد نمونه جاری از کلاس (this) را بر میگرداند. ابتدا یک کلاس با نام FluentConnectionStringBuilder ایجاد کرده و محتوای آن را به شکل زیر تغییر دهید:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | public class FluentConnectionStringBuilder { private string _server; private string _database; private string _username; private string _password; public FluentConnectionStringBuilder ForServer(string server) { _server = server; return this; } public FluentConnectionStringBuilder AndDatabase(string database) { _database = database; return this; } public FluentConnectionStringBuilder WithUsername(string username) { _username = username; return this; } public FluentConnectionStringBuilder AndPassword(string password) { _password = password; return this; } public string Build() => $"Server={_server};Database={_database};User Id={_username};Password={_password};"; } |
همانطور که مشاهده می کنید، هر متد بعد از انجام کارش، نمونه جاری از کلاس را باز میگرداند تا بتوانیم سایر متدها را نیز بعد از آن فراخوانی کنیم.
حال می توانیم از کلاس فوق به صورت زیر استفاده کنیم:
1 2 3 4 5 6 7 8 9 | var connectionString = new FluentConnectionStringBuilder() .ForServer("127.0.0.1") .AndDatabase("AwesomeCSharpAppDb") .WithUsername("db_user") .AndPassword("db_user_password") .Build(); Console.WriteLine(connectionString); // Output: Server=127.0.0.1;Database=AwesomeCSharpAppDb;User Id=db_user;Password=db_user_password; |
کد کامل مثال فوق:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 | using System; public class FluentConnectionStringBuilder { private string _server; private string _database; private string _username; private string _password; public FluentConnectionStringBuilder ForServer(string server) { _server = server; return this; } public FluentConnectionStringBuilder AndDatabase(string database) { _database = database; return this; } public FluentConnectionStringBuilder WithUsername(string username) { _username = username; return this; } public FluentConnectionStringBuilder AndPassword(string password) { _password = password; return this; } public string Build() => $"Server={_server};Database={_database};User Id={_username};Password={_password};"; } internal class Program { internal static void Main() { var connectionString = new FluentConnectionStringBuilder() .ForServer("127.0.0.1") .AndDatabase("AwesomeCSharpAppDb") .WithUsername("db_user") .AndPassword("db_user_password") .Build(); Console.WriteLine(connectionString); // Output: Server=127.0.0.1;Database=AwesomeCSharpAppDb;User Id=db_user;Password=db_user_password; } } |
محدود سازی انتخاب در Fluent API
اگر مثال بالا را تست کرده باشید، متوجه می شوید که کاربر هیچ محدودیتی در انتخاب متدها ندارد و هر کدام را که بخواهد با هر ترتیبی می تواند انتخاب کند. مثلا می تواند فقط متد Build را فراخوانی کند که در این صورت رشته اتصال تولید شده ناقص خواهد بود. برای محدود سازی انتخاب متد در این روش، می توانیم از Interface استفاده کنیم.
در مثال بالا ما ابتدا می خواهیم که سرور انتخاب شود. پس یک اینترفیس با نام IServerSelectionStage ایجاد می کنیم که یک متد با نام ForServer دارد. بعد از آن می خواهیم دیتابیس انتخاب شود. پس یک اینترفیس دیگر با نام IDatabaseSelectionStage ایجاد می کنیم که یک متد با نام AndDatabase دارد. بعد از آن می خواهیم نام کاربری دیتابیس انتخاب شود. پس یک اینترفیس دیگر با نام IUsernameSelectionStage ایجاد می کنیم که یک متد با نام WithUsername دارد. بعد از آن می خواهیم پسورد انتخاب شود. پس یک اینترفیس دیگر با نام IPasswordSelectionStage ایجاد می کنیم که یک متد با نام AndPassword دارد. در نهایت می خواهیم متد Build فراخوانی شود. پس یک اینترفیس دیگر با نام IBuilderSelectionStage ایجاد می کنیم که یک متد با نام Build دارد.
قسمت مهم در این طراحی، خروجی متدهای تعریف شده است. برای مثال اگر بخواهیم ترتیب فراخوانی به شکل زیر باشد:
1 | ForServer -> AndDatabase -> WithUsername -> AndPassword -> Build |
باید نوع خروجی متدها به این صورت تعریف شود:
1 2 3 4 5 | ForServer -> IDatabaseSelectionStage AndDatabase -> IUsernameSelectionStage WithUsername -> IPasswordSelectionStage AndPassword -> IBuilderSelectionStage Build -> string |
حال مثال بالا را به این صورت تغییر می دهیم. اینترفیس های مورد نیاز:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | public interface IServerSelectionStage { IDatabaseSelectionStage ForServer(string server); } public interface IDatabaseSelectionStage { IUsernameSelectionStage AndDatabase(string database); } public interface IUsernameSelectionStage { IPasswordSelectionStage WithUsername(string username); } public interface IPasswordSelectionStage { IBuilderSelectionStage AndPassword(string password); } public interface IBuilderSelectionStage { string Build(); } |
محتوای تغییر یافته کلاس FluentConnectionStringBuilder:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | public class FluentConnectionStringBuilder : IServerSelectionStage, IDatabaseSelectionStage, IUsernameSelectionStage, IPasswordSelectionStage, IBuilderSelectionStage { private string _server; private string _database; private string _username; private string _password; private FluentConnectionStringBuilder() {} public static IServerSelectionStage Create() { return new FluentConnectionStringBuilder(); } public IDatabaseSelectionStage ForServer(string server) { _server = server; return this; } public IUsernameSelectionStage AndDatabase(string database) { _database = database; return this; } public IPasswordSelectionStage WithUsername(string username) { _username = username; return this; } public IBuilderSelectionStage AndPassword(string password) { _password = password; return this; } public string Build() => $"Server={_server};Database={_database};User Id={_username};Password={_password};"; } |
اگر به کد فوق توجه کنید، متوجه می شوید که کلاس FluentConnectionStringBuilder کل اینترفیس های بالا را پیادهسازی کرده است. با این کار هنگامی که در داخل متد نمونه جاری کلاس را باز میگردانیم (return this)، آن نمونه به اینترفیسی که برای نوع بازگشتی متد تعریف شده است تبدیل می شود. بنابراین فقط به متد تعریف شده در آن اینترفیس دسترسی داریم. همچنین سازنده کلاس را به صورت private تعریف کرده ایم که امکان نمونه سازی از این کلاس فقط برای اعضای آن امکانپذیر باشد و از آن جایی که در بالا گفتیم می خواهیم اول از همه سرور انتخاب شود، پس یک متد استاتیک با نام Create ایجاد می کنیم که نوع بازگشتی آن IServerSelectionStage است و خیلی ساده یک نمونه از کلاس ایجاد کرده و باز میگرداند. نمونه ایجاد شده به IServerSelectionStage تبدیل می شود و ما فقط به متد ForServer دسترسی خواهیم داشت.
کد کامل مثال به این صورت خواهد بود:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 | using System; public interface IServerSelectionStage { IDatabaseSelectionStage ForServer(string server); } public interface IDatabaseSelectionStage { IUsernameSelectionStage AndDatabase(string database); } public interface IUsernameSelectionStage { IPasswordSelectionStage WithUsername(string username); } public interface IPasswordSelectionStage { IBuilderSelectionStage AndPassword(string password); } public interface IBuilderSelectionStage { string Build(); } public class FluentConnectionStringBuilder : IServerSelectionStage, IDatabaseSelectionStage, IUsernameSelectionStage, IPasswordSelectionStage, IBuilderSelectionStage { private string _server; private string _database; private string _username; private string _password; private FluentConnectionStringBuilder() {} public static IServerSelectionStage Create() { return new FluentConnectionStringBuilder(); } public IDatabaseSelectionStage ForServer(string server) { _server = server; return this; } public IUsernameSelectionStage AndDatabase(string database) { _database = database; return this; } public IPasswordSelectionStage WithUsername(string username) { _username = username; return this; } public IBuilderSelectionStage AndPassword(string password) { _password = password; return this; } public string Build() => $"Server={_server};Database={_database};User Id={_username};Password={_password};"; } internal class Program { internal static void Main() { var connectionString = FluentConnectionStringBuilder .Create() .ForServer("127.0.0.1") .AndDatabase("AwesomeCSharpAppDb") .WithUsername("db_user") .AndPassword("db_user_password") .Build(); Console.WriteLine(connectionString); // Output: Server=127.0.0.1;Database=AwesomeCSharpAppDb;User Id=db_user;Password=db_user_password; } } |
هیچ نظری ثبت نشده است