1.问题
假设你要实现一个简单的构造者模式,且要符合fluent Api。你的实体对象为公司雇员:
namespace RecursiveGeneric
{
internal class Employee
{
public string Name { get; set; }=String.Empty;
public string Position { get; set; }= String.Empty;
public double Salary { get; set; }
public override string ToString()
{
return $"Name: {Name}, Position: {Position}, Salary: {Salary}";
}
}
}
你首先有个类,专门用于创建雇员信息:
internal class EmployeeInfoBuilder
{
protected Employee employee = new Employee();
public EmployeeInfoBuilder SetName(string name)
{
employee.Name = name;
return this;
}
}
接着我们定义了一个设置位置的类:
internal class EmployeePositionBuilder:EmployeeInfoBuilder
{
public EmployeePositionBuilder AtPosition(string position)
{
employee.Position = position;
return this;
}
}
之所以这么做是为了重复利用我们的对象,这样可以更灵活的定制我们的雇员,所以我们想再构造时能这样写:
var builder = new EmployeeInfoBuilder();
builder.SetName("王杰")
.AtPosition()
.Build();
但实际上解释器就会报错:
原因很简单,SetName函数返回的对象类型是EmployeeInfoBuilder,无法调用EmployeePositionBuilder类的函数成员。当然我们可以将Employee对象以构造注入的方式传入进去,但是这样就违背了我们的Fluent原则。
2.解决办法
为了能够变化类型,我们应该从泛型着手。首先定义一个基本类型:
/// <summary>
/// 负责实例化和提供员工对象
/// </summary>
internal abstract class EmployeeBuilder
{
protected Employee employee;
public EmployeeBuilder()
{
employee = new Employee();
}
public Employee Build()=>employee;
}
重点是EmployeeInfoBuilder泛型如何设计?
假设我们的泛型参数是下一个要调用的类,也就是上面的EmployeePositionBuilder,我们实验一下:
显然这样是不行的,且为了后续拓展,EmployeePositionBuilder也应该是泛型。那我们修改一下:
但是这样还是无法实现,为了能成功转化:
internal class EmployeeInfoBuilderTest<T>:EmployeeBuilder where T:EmployeeInfoBuilderTest<T>
{
public T SetName(string name)
{
employee.Name = name;
return (T)this;
}
}
现在可以了,那T必须是EmployeePositionBuilderTest<T>,所以为了能传入进去,定义为:
internal class EmployeePositionBuilderTest<T>:EmployeeInfoBuilderTest<EmployeePositionBuilderTest<T>> where
T:EmployeePositionBuilderTest<T>
{
public T AtPosition(string position)
{
employee.Position = position;
return (T)this;
}
}
这样在语法上合格了,可以我们怎么调用呢?
对于上面两个泛型类,我们无法直接定义,只能借助更高层次的派生类构造,且这个类不能再是泛型,也就是泛型链的终点。
internal class EmployeeBuilderDirectorTest:EmployeePositionBuilderTest<EmployeeBuilderDirectorTest>
{
public static EmployeeBuilderDirector NewEmployee = new EmployeeBuilderDirector();
}
所以我们可以这样使用:
EmployeeInfoBuilderTest<EmployeePositionBuilderTest<EmployeeBuilderDirectorTest>> test =
new EmployeeInfoBuilderTest<EmployeePositionBuilderTest<EmployeeBuilderDirectorTest>>();
var employee = test.SetName("小红").AtPosition("北京").Build();
调用链有点长,其实可以简化:
var employee = EmployeeBuilderDirectorTest.NewEmployee.SetName("猴王")
.AtPosition("花果山").Build();
这么做可行的原因是EmoployeeBuilderDirectorTest是逐即派生的,所以拥有上述泛型类的成员函数。这么写确实会有一点绕,需要多揣摩一下。
最后整理一下
internal class Employee
{
public string Name { get; set; }=String.Empty;
public string Position { get; set; }= String.Empty;
public double Salary { get; set; }
public override string ToString()
{
return $"Name: {Name}, Position: {Position}, Salary: {Salary}";
}
}
internal class EmployeeInfoBuilder<T>:EmployeeBuilder where T:EmployeeInfoBuilder<T>
{
public T SetName(string name)
{
employee.Name=name;
return (T)this;
}
}
internal class EmployeePositionBuilder<T>:EmployeeInfoBuilder<EmployeePositionBuilder<T>> where T:EmployeePositionBuilder<T>
{
public T AtPosition(string position)
{
employee.Position = position;
return (T)this;
}
}
internal class EmployeeSalaryBuilder<T>:EmployeePositionBuilder<EmployeeSalaryBuilder<T>>
where T : EmployeeSalaryBuilder<T>
{
public T WithSalary(double salary)
{
employee.Salary = salary;
return (T)this;
}
}
internal class EmployeeBuilderDirector:EmployeeSalaryBuilder<EmployeeBuilderDirector>
{
public static EmployeeBuilderDirector NewEmployee = new EmployeeBuilderDirector();
}
调用:
var e=EmployeeBuilderDirector.NewEmployee.SetName("小王")
.AtPosition("上海")
.WithSalary(20000)
.Build();
Console.WriteLine(e);
稍微说明一下的就是,这么做一方面是不好理解,另一方面可能对性能有一定的影响,我认为这个派生链条不宜过长,希望有懂的朋友可以指点一下。