Для возврата `this` из универсального родительского класса требуется приведение в дочерний класс


Я использую шаблон builder , извлекаю повторяющийся код в класс "helper", но есть один аспект повторяющегося кода, которым я все еще не доволен.

Шаблон builder позволяет построить цепочку реализации кода следующим образом:

Car car = new CarBuilder().Wheels(4).Convertible().Build();

Каждый из методов CarBuilder, Wheels и Convertible возвращает один и тот же экземпляр класса builder (return this), а метод Build возвращает только что созданный экземпляр Car.

Вот моя попытка создать универсальный конструктор класс:

public class Builder<T> where T : class 
{
    private Func<T, T> func;

    protected void SetInstantiator(Func<T, T> f) => this.func = f;

    protected void Chain(Action<T> action)
    {
        this.ChainFunc(action);
    }

    private ChainFunc(Action<T> action)
    {
        // SNIPPED
    }

    protected T Instantiate() => this.func(null);
}

И вот реализация моего универсального конструктора:

public class CarBuilder : Builder<Car>
{
    public CarBuilder()
    {
        this.SetInstantiator(c => new Car());
        return this;
    }

    public CarBuilder Wheels(int wheels)
    {
        this.Chain(c => c.SetWheelCount(wheels));
        return this;
    }

    public CarBuilder Convertible()
    {
        this.Chain(c => c.RetractableRoof = true);
        return this;
    }

    public Car Build() => this.Instantiate();
}

Что меня беспокоит, так это повторение return this после каждого вызова метода Chain и мысль, что я мог бы протолкнуть это в сам метод Chain, т. е. я хочу написать код так:

    public CarBuilder Wheels(int wheels) =>
        this.Chain(c => c.SetWheelCount(wheels));

В классе builder я попытался изменить тип возвращаемого значения с void на Builder:

protected Builder Chain(Action<T> action)
{
    this.ChainFunc(action);
    return this;
}

... но компилятор говорит, что возвращаемый тип должен быть Builder<T>, т. е.

protected Builder<T> Chain(Action<T> action)
{
    this.ChainFunc(action);
    return this;
}

Ладно, достаточно честно., но в моем классе реализации я теперь должен сделать приведение:

    public CarBuilder Wheels(int wheels) =>
        (CarBuilder)this.Chain(c => c.SetWheelCount(wheels));

Итак, я снова повторил код, в котором все методы теперь должны включать приведение. Передача типа класса из подтипа в супертип не кажется правильной.

Мне кажется, я упускаю здесь что-то фундаментальное. Могу ли я избежать как повторения приведения, так и необходимости "возвращать это" из каждого метода реализации builder?
2 3

2 ответа:

Один из способов сохранить логику в защищенной области - это добавить статический метод, который вызывается вместо метода экземпляра. Статический метод может использовать неявное приведение для возврата типа вызывающего объекта

Внутри Builder<T>

protected void Chain(Action<T> action)
{
    //local chain logic
}

protected static BT Chain<BT>(BT builder, Action<T> action)
    where BT:Builder<T>
{
    builder.Chain(action);
    return builder;
}

Вызовы внутри CarBuilder:

public CarBuilder Wheels(int wheels) => Chain(this , c => c.SetWheelCount(wheels));

public CarBuilder Convertible() => Chain(this, c => c.RetractableRoof = true);

Не помещайте Chain в базовый класс. Вместо этого сделайте его универсальным методом расширения:

public static TBuilder Chain<TBuilder, TObject>(this TBuilder @this, Action<TObject> a)
 where TBuilder: Builder<TObject>
 => ...