Свободные интерфейсы и наследование в C#
я покажу проблему на примере. Существует базовый класс с плавным интерфейсом:
class FluentPerson
{
private string _FirstName = String.Empty;
private string _LastName = String.Empty;
public FluentPerson WithFirstName(string firstName)
{
_FirstName = firstName;
return this;
}
public FluentPerson WithLastName(string lastName)
{
_LastName = lastName;
return this;
}
public override string ToString()
{
return String.Format("First name: {0} last name: {1}", _FirstName, _LastName);
}
}
и дочерний класс:
class FluentCustomer : FluentPerson
{
private long _Id;
private string _AccountNumber = String.Empty;
public FluentCustomer WithAccountNumber(string accountNumber)
{
_AccountNumber = accountNumber;
return this;
}
public FluentCustomer WithId(long id)
{
_Id = id;
return this;
}
public override string ToString()
{
return base.ToString() + String.Format(" account number: {0} id: {1}", _AccountNumber, _Id);
}
}
проблема в том, что когда вы называете customer.WithAccountNumber("000").WithFirstName("John").WithLastName("Smith") вы не можете добавить .WithId(123) В конце концов, потому что тип возвращаемого значения WithLastName() метод FluentPerson (не FluentCustomer).
как эта проблема обычно решается?
6 ответов:
вы можете использовать дженерики для достижения этой цели.
public class FluentPerson<T> where T : FluentPerson<T> { public T WithFirstName(string firstName) { // ... return (T)this; } public T WithLastName(string lastName) { // ... return (T)this; } } public class FluentCustomer : FluentPerson<FluentCustomer> { public FluentCustomer WithAccountNumber(string accountNumber) { // ... return this; } }и так:
var customer = new FluentCustomer() .WithAccountNumber("123") .WithFirstName("Abc") .WithLastName("Def") .ToString();
попробуйте использовать некоторые методы расширения.
static class FluentManager { public static T WithFirstName<T>(this T person, string firstName) where T : FluentPerson { person.FirstName = firstName; return person; } public static T WithId<T>(this T customer, long id) where T : FluentCustomer { customer.ID = id; return customer; } } class FluentPerson { public string FirstName { private get; set; } public string LastName { private get; set; } public override string ToString() { return string.Format("First name: {0} last name: {1}", FirstName, LastName); } } class FluentCustomer : FluentPerson { public long ID { private get; set; } public long AccountNumber { private get; set; } public override string ToString() { return base.ToString() + string.Format(" account number: {0} id: {1}", AccountNumber, ID); } }после того, как вы можете использовать как
new FluentCustomer().WithId(22).WithFirstName("dfd").WithId(32);
логически вам нужно настроить материал от самого конкретного (клиента) до наименее конкретного (человека) или иначе его даже трудно прочитать, несмотря на свободный интерфейс. Следуя этому правилу в большинстве случаев вам не нужно будет попадать в неприятности. Если, однако, по какой-либо причине вам все еще нужно смешать его, вы можете использовать промежуточные подчеркивающие операторы, такие как
static class Customers { public static Customer AsCustomer(this Person person) { return (Customer)person; } } customer.WIthLastName("Bob").AsCustomer().WithId(10);
решение, где вам нужно, интерфейс, наследование, а также некоторые обобщения...
во всяком случае, как я уже говорил ранее: это единственный вариант, если вы хотите использовать наследование и доступ к защищенным членам...
public class GridEx<TC, T> where TC : GridEx<TC, T> { public TC Build(T type) { return (TC) this; } } public class GridExEx : GridEx<GridExEx, int> { } class Program { static void Main(string[] args) { new GridExEx().Build(1); } }
public class FluentPerson { private string _FirstName = String.Empty; private string _LastName = String.Empty; public FluentPerson WithFirstName(string firstName) { _FirstName = firstName; return this; } public FluentPerson WithLastName(string lastName) { _LastName = lastName; return this; } public override string ToString() { return String.Format("First name: {0} last name: {1}", _FirstName, _LastName); } } public class FluentCustomer { private string _AccountNumber = String.Empty; private string _id = String.Empty; FluentPerson objPers=new FluentPerson(); public FluentCustomer WithAccountNumber(string accountNumber) { _AccountNumber = accountNumber; return this; } public FluentCustomer WithId(string id) { _id = id; return this; } public FluentCustomer WithFirstName(string firstName) { objPers.WithFirstName(firstName); return this; } public FluentCustomer WithLastName(string lastName) { objPers.WithLastName(lastName); return this; } public override string ToString() { return objPers.ToString() + String.Format(" account number: {0}", _AccountNumber); } }и вызвать его с помощью
var ss = new FluentCustomer().WithAccountNumber("111").WithFirstName("ram").WithLastName("v").WithId("444").ToString();
является ли свободный интерфейс действительно лучшим вызовом здесь, или инициализатор будет лучше?
var p = new Person{ LastName = "Smith", FirstName = "John" }; var c = new Customer{ LastName = "Smith", FirstName = "John", AccountNumber = "000", ID = "123" };В отличие от свободного интерфейса, это прекрасно работает без унаследованных методов, возвращающих базовый класс и испортить цепочку. Когда вы наследуете свойство, вызывающий действительно не должен заботиться о том,
FirstNameбыл впервые реализован лично или заказчиком или объектом.Я нахожу это более читаемым, как на одной строке, так и на нескольких,и у тебя нет чтобы пройти через проблемы обеспечения беглого самостоятельного декорирования функций, которые соответствуют каждому свойству.
Comments