了解如何创建流畅的界面

嗨,我正在尝试理解如何构建一个可读的,也可以防止Fluent-API出错,而不会对用户造成太大限制

保持简单让我们说我们想要改变下面的课程,以便流利

public class Car { public int Gallons { get; private set; } public int Tons { get; private set; } public int Bhp { get; private set; } public string Make { get; private set; } public string Model { get; private set; } public Car(string make, string model) { Make = make; Model = model; } public void WithHorsePower(int bhp) { Bhp = bhp; return this; } public void WithFuel(int gallons) { Gallons = gallons; } public void WithWeight(int tons) { Tons = tons; } public int Mpg() { return Gallons/Tons; } } 

在这种情况下的问题是,如果首先调用Weight()Fuel()则用户应该只能访问Mpg()HorsePower()的位置也无关紧要。

样品:

 int mpg =Car.Create().HorsePower().Fuel().Weight().Mpg(); int mpg =Car.Create().Fuel().HorsePower().Weight().Mpg(); int mpg =Car.Create().HorsePower().Fuel().HorsePower().Weight().Mpg();// <- no typo int mpg =Car.Create().Fuel().Weight().HorsePower().Mpg(); int mpg =Car.Create().Weight().HorsePower().Fuel().Mpg(); int mpg =Car.Create().Weight().Fuel().Mpg(); 

没有大量的接口,有没有一种简单的方法可以做到这一点?
我也不知道如何以正确的方式实现这个嵌套接口

这是我目前创建的接口

 interface Start { IFuelWeight1 HorsePower(); IHorsePowerWeight1 Fuel(); IHorsePowerFuel1 Weight(); } interface IFuelWeight1 // Start.HorsePower() { IHorsePowerWeight1 Fuel(); IHorsePowerFuel1 Weight(); } interface IHorsePowerWeight1 // Start.Fuel() { IHorsePowerWeight1 HorsePower(); IHorsePowerFuelMpg Weight(); } interface IHorsePowerFuel1 // Start.Weight() { IHorsePowerFuel1 HorsePower(); IHorsePowerWeightMpg Fuel(); } #region End interface IHorsePowerFuelMpg { IFuelWeightMpg HorsePower(); IHorsePowerWeightMpg Fuel(); int Mpg(); } interface IHorsePowerWeightMpg { IFuelWeightMpg HorsePower(); IHorsePowerFuelMpg Weight(); int Mpg(); } interface IFuelWeightMpg { IHorsePowerWeightMpg Fuel(); IHorsePowerFuelMpg Weight(); int Mpg(); } #endregion 

编辑 Adam Houldsworth 🙂

  • 界面是上面的好还是有更简单的方法来做到这一点但是对Mpg()有限制吗?
  • 如何实现上面的接口来做到这一点?:

      var k = myMiracle as Start; k.Fuel().Weight(); k.Weight().Fuel(); k.HorsePower().Fuel().Weight(); k.HorsePower().Weight().Fuel(); k.Fuel().HorsePower().Weight(); k.Weight().HorsePower().Fuel(); 

一种替代方法是调用Mpg()上的所有操作,这将允许其他操作是有条件的。

这已经在SO中用代码示例回答了。 请参阅条件生成器方法链接流畅界面

post指出,使用构造函数可以实现相同的操作,而不是接口,使用调用方法使所有其他操作成为条件。

流畅的API是一件好事,但我会采取不同的方式。 建造汽车吸引了我更多的建筑师模式 。 这样你就可以隐藏一个工厂组成的汽车(不是工厂方法模式),它接受你现在拥有的命令,但不接受问题。

除非已完成并准备公布,否则没有制造商会让您了解有关新车的详细信息。 因此,您必须首先发送一个像GetMyCar()这样的命令来释放汽车,如果您在未完成的汽车上调用Mpg ,您将获得exception,这将是非常有意义的。 如果你使用那种流畅的模式,它仍然会很好看。

 var builder = new CarBuilder(); // each building method returns `CarBuilder` builder.BuildFrames(size).BuildChassis().AppendWheels(4)... 

好的,这是我的看法。 但是,如果您不喜欢构建器,还有两个针对您当前情况的建议。

1)如果用户在设置WeightFuel之前调用Mpg ,则会抛出exception,并显示解释该情况的消息。 还要添加适当的Mpg方法文档。

2)使构造函数获取其他属性的所有必需参数。 在我看来,这是一个比第一个更好的解决方案,因为从一开始就说明你可以期待什么。