使用接口编写流畅风格 API 时避免代码重复
Avoiding code duplication when writing fluent-style API using interfaces
我正在尝试为在我的 C# 程序中构建实体编写流畅的 API。
我有一个基本实体,它有一些属性和多态实体的集合:它们都来自一个公共基本类型,但有些会有额外的属性。
这是一个例子:
public interface IGarageBuilder
{
public IGarageBuilder Label(string label);
public IGarageBuilder AddCar(Action<ICarBuilder> configure);
public IGarageBuilder AddBike(Action<IBikeBuilder > configure);
}
public interface IVehicleBuilder
{
public IVehicleBuilder Model(string model); // <- That is the member that is causing me trouble
}
public interface IBikeBuilder : IVehicleBuilder
{
}
public interface ICarBuilder : IVehicleBuilder
{
public ICarBuiler HorsePoser(int horsePower);
}
(我写的软件有很多IVehicleBuilder共有的属性,我简化了很多)
我遇到了 IVehicleBuilder 接口的问题:如上定义,以下代码将无法编译:
public void BuildGarage(IGarageBuilder builder)
{
builder.Label("My dream garage")
.AddCar(opt => opt.Model("Ford").HorsePower(120)); // <- this fails because .Model() returns a IVehicleBuilder interface, not an ICarBuilder
}
现在,我可以在没有 IVehicleBuilder 和 ICarBuilder 之间的依赖关系的情况下将“Model()”API 复制到 ICarBuilder 和 IBikeBuilder,但是,在我的真实应用程序中,我有几十个这样的 API,其中几个被覆盖以允许不同的方式来配置实体。此外,我还必须复制这些实现,因为它们现在 return 不同的类型。
有没有办法避免所有这些代码重复?
减少重复的一种方法是将界面分为两层。一个级别描述了可用的不同种类的方法(或方法组),更高级别将这些组合在一起成为 API 使用的流畅接口,使用接口继承:
public interface IHasModel<T>
{
T Model(string model);
}
public interface ICarBuilder : IHasModel<ICarBuilder>
{
// Things which are specific to just cars can go in here if you want, rather than
// taking up a separate IHasHorsePower<T>
ICarBuilder HorsePower(int horsePower);
}
public interface IBikeBuilder : IHasModel<IBikeBuilder> { }
此外,尝试为所有接口保持相同的底层构建器实现。这可以具有仅由单个构建器使用的方法和状态位,这很好。
internal class Builder : ICarBuilder, IBikeBuilder
{
// May or may not be used, depending on what we're building...
private string model;
private int horsePower;
public Builder Model(string model)
{
this.model = model;
return this;
}
// Annoyingly return type covariance isn't yet supported for implicit interface
// implementations, so we need this boilerplate
ICarBuilder IHasModel<ICarBuilder>.Model(string model) => Model(model);
IBikeBuilder IHasModel<IBikeBuilder>.Model(string model) => Model(model);
// You can avoid the song-and-dance for simple things, which are just referenced
// by a single interface
public ICarBuilder HorsePower(int horsePower)
{
this.horsePower = horsePower;
return this;
}
// I assume you'll have something like this as well...
public Car BuildCar() => new Car(model, horsePower);
public Bike BuildBike() => new Bike(model);
}
我正在尝试为在我的 C# 程序中构建实体编写流畅的 API。
我有一个基本实体,它有一些属性和多态实体的集合:它们都来自一个公共基本类型,但有些会有额外的属性。
这是一个例子:
public interface IGarageBuilder
{
public IGarageBuilder Label(string label);
public IGarageBuilder AddCar(Action<ICarBuilder> configure);
public IGarageBuilder AddBike(Action<IBikeBuilder > configure);
}
public interface IVehicleBuilder
{
public IVehicleBuilder Model(string model); // <- That is the member that is causing me trouble
}
public interface IBikeBuilder : IVehicleBuilder
{
}
public interface ICarBuilder : IVehicleBuilder
{
public ICarBuiler HorsePoser(int horsePower);
}
(我写的软件有很多IVehicleBuilder共有的属性,我简化了很多)
我遇到了 IVehicleBuilder 接口的问题:如上定义,以下代码将无法编译:
public void BuildGarage(IGarageBuilder builder)
{
builder.Label("My dream garage")
.AddCar(opt => opt.Model("Ford").HorsePower(120)); // <- this fails because .Model() returns a IVehicleBuilder interface, not an ICarBuilder
}
现在,我可以在没有 IVehicleBuilder 和 ICarBuilder 之间的依赖关系的情况下将“Model()”API 复制到 ICarBuilder 和 IBikeBuilder,但是,在我的真实应用程序中,我有几十个这样的 API,其中几个被覆盖以允许不同的方式来配置实体。此外,我还必须复制这些实现,因为它们现在 return 不同的类型。
有没有办法避免所有这些代码重复?
减少重复的一种方法是将界面分为两层。一个级别描述了可用的不同种类的方法(或方法组),更高级别将这些组合在一起成为 API 使用的流畅接口,使用接口继承:
public interface IHasModel<T>
{
T Model(string model);
}
public interface ICarBuilder : IHasModel<ICarBuilder>
{
// Things which are specific to just cars can go in here if you want, rather than
// taking up a separate IHasHorsePower<T>
ICarBuilder HorsePower(int horsePower);
}
public interface IBikeBuilder : IHasModel<IBikeBuilder> { }
此外,尝试为所有接口保持相同的底层构建器实现。这可以具有仅由单个构建器使用的方法和状态位,这很好。
internal class Builder : ICarBuilder, IBikeBuilder
{
// May or may not be used, depending on what we're building...
private string model;
private int horsePower;
public Builder Model(string model)
{
this.model = model;
return this;
}
// Annoyingly return type covariance isn't yet supported for implicit interface
// implementations, so we need this boilerplate
ICarBuilder IHasModel<ICarBuilder>.Model(string model) => Model(model);
IBikeBuilder IHasModel<IBikeBuilder>.Model(string model) => Model(model);
// You can avoid the song-and-dance for simple things, which are just referenced
// by a single interface
public ICarBuilder HorsePower(int horsePower)
{
this.horsePower = horsePower;
return this;
}
// I assume you'll have something like this as well...
public Car BuildCar() => new Car(model, horsePower);
public Bike BuildBike() => new Bike(model);
}