使用接口编写流畅风格的 API 时避免代码重复

问题描述

我正在尝试编写一个流畅的 API,用于在我的 C# 程序中构建实体。

我有一个基础实体,它有一些属性和一组多态实体:它们都来自一个共同的基础类型,但有些会有额外的属性。

这是一个例子:

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,其中几个被覆盖以允许配置实体的不同方式。此外,我还必须复制这些实现,因为它们现在返回不同的类型。

有没有办法避免所有这些代码重复?

解决方法

减少重复的一种方法是将您的界面分为两个级别。一个级别描述了可用的不同类型的方法(或方法组),更高级别使用接口继承将这些方法组合成 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);
}

相关问答

依赖报错 idea导入项目后依赖报错,解决方案:https://blog....
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下...
错误1:gradle项目控制台输出为乱码 # 解决方案:https://bl...
错误还原:在查询的过程中,传入的workType为0时,该条件不起...
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct...