递归泛型(recursive generic)

 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);

        稍微说明一下的就是,这么做一方面是不好理解,另一方面可能对性能有一定的影响,我认为这个派生链条不宜过长,希望有懂的朋友可以指点一下。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值