总的来说,在这个问题之后,我决定写一个例子,但不幸的是我没有掌握它😢。也许你们中的一个可以帮我完成它。
我们有这样一类人
class Person
{
public Person(string firstName, string lastName,
Address address, Employment employment)
{
FirstName = firstName;
LastName = lastName;
Address = address;
Employment = employment;
}
public string FirstName { get; }
public string LastName { get; }
public Address Address { get; }
public Employment Employment { get; }
public override string ToString()
{
return $"{FirstName} {LastName}: "
+ Environment.NewLine +
$"{Address}"
+ Environment.NewLine +
$"{Employment}";
}
}
与同伴课程
class Address
{
public string City { get; set; }
public int PostCode { get; set; }
public override string ToString()
{
return $"Адрес: {City}-{PostCode} ";
}
}
class Employment
{
public string CompanyName { get; set; }
public string Position { get; set; }
public int AnnualIncome { get; set; }
public override string ToString()
{
return $"Работа: {CompanyName}, должность: {Position}, зарплата: {AnnualIncome}";
}
}
独立设法只编写这样一个构建器
class PersonBuilder
{
//аккумулятор названий свойств и их значений
private readonly Dictionary<string, object> _propertiesToBuild;
public PersonBuilder()
{
_propertiesToBuild = new Dictionary<string, object>();
}
//запоминание в словаре названия свойства и его значения
public PersonBuilder Set<T>(Expression<Func<Person, T>> expression, T value)
{
var propertyName = ((MemberExpression)expression.Body).Member.Name;
_propertiesToBuild.Add(propertyName, value);
return this;
}
public T Include<T>(Expression<Func<Person, T>> expression)
{
var propertyName = ((MemberExpression)expression.Body).Member.Name;
if (propertyName == nameof(Person.Address))
{
var result = new Address();
return (T)(object)result;
}
else
{
var result = new Employment();
return (T)(object)result;
}
}
//создание экземпляра на основе значений свойств из словаря
public Person Build()
{
return new Person
(
firstName: GetPropertyValue<string>(nameof(Person.FirstName), "Неизвестно"),
lastName: GetPropertyValue<string>(nameof(Person.LastName), "Неизвестно"),
address: new Address(),
employment: new Employment()
);
}
private T GetPropertyValue<T>(string propertyName, T defaultValue)
{
return _propertiesToBuild.TryGetValue(propertyName, out var value) ? (T)value : defaultValue;
}
}
虽然它是这样工作的
var person = new PersonBuilder()
.Set(p => p.FirstName, "Андрей")
.Set(p => p.LastName, "Иванов")
.Build();
或者
Address address = new PersonBuilder()
.Set(p => p.FirstName, "Андрей")
.Set(p => p.LastName, "Иванов")
.Include(p => p.Address);
或者像这样
Employment employment = new PersonBuilder()
.Set(p => p.FirstName, "Андрей")
.Set(p => p.LastName, "Иванов")
.Include(p => p.Employment);
我希望结果是这样的
Person person = new PersonBuilder()
.Set(p => p.FirstName, "Андрей")
.Set(p => p.LastName, "Иванов")
.Include(p => p.Address)
.Set(a => a.City, "Москва")
.Include(p => p)
.Include(p => p.Employment)
.Set(e => e.Position, "инженер")
.Include(p => p)
.Build();
PS结果版本在这里,谢谢@Vasek
В качестве пропаганды всего нового и хорошего напомню, что immutable + builder из коробки поддерживается 9-ой версией C#.
Добавляем в .csproj
<LangVersion>9.0</LangVersion>
, и ваш пример можно магически записать следующим образом:А код, использующий всё это, запишется так:
Всё!
这样的解决方案怎么样?
UPD:发现了一个错误。如果同一类型在树中多次出现,它将不起作用。例如,如果你
Person
有Address WorkAddress
和Address HomeAddress
。我会考虑如何破解它。值得在这里添加防御性编程,例如,必须隐藏设计器,并通过反射拉取。可能是他没有考虑到。
稍微改了一下数据
这就是发生的事情
用法
控制台输出
Ps: 我想出了这个。从它是必要的事实出发,尽可能简单。我的表达能力不是很强,所以可能有些地方不太对劲,但我认为这个想法会很清楚
我会批评问题本身的陈述。
PersonBuilder 的含义是什么?这是某种人工实体,实际上是不可变类的可变对应物,需要为每个此类复制。以统一的方式构建对象不是更好吗?
.Set(p => p.FirstName, "Андрей")
- использование лямбда-выражения ни с того, ни с сего, хотя анонимная функция тут вообще не вызывается. Почему не.Set("FirstName", "Андрей")
?.Include(p => p)
- вообще не понятно, что такое. Как включить самого себя? По логике, происходит переход к родительскому объекту, так что лучше бы создать отдельный метод "ToParent"Я бы сделал это так. Допустим, мы принимаем соглашение, что у иммутабельного класса имена get-only свойств начинаются с большой буквы. Параметры конструктора соответствуют по типу его свойствам, а имя параметра начинается путем замены первой буквы имени свойства на такую же маленькую букву. Создадим такой класс:
Его суть в использовании выражений для вызова конструктора объекта и получения значений свойств. Так можно строить любой объект, который удовлетворяет нашим соглашениям.
Сделаем иммутабельные классы производными от него:
Тогда использовать можно так: