了解如何创建流畅的界面

时间:2013-09-17 10:00:32

标签: c# fluent fluent-interface

您好我正在努力了解我如何构建可读性和错误防止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;
    }
}

在这种情况下,如果首先调用Mpg()Weight()并且Fuel()的位置无关,则用户应该只能访问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();
    

2 个答案:

答案 0 :(得分:8)

Fluent API是一件好事,但我会采取不同的方式。建造一辆汽车让我更加关注Builder pattern。这样你就可以隐藏在工厂中组成的汽车(不是工厂方法模式),它接受你现在拥有的命令,但是不接受问题。

没有制造商会让您知道有关新车的详细信息,除非它已完成并准备公布。因此您必须先发送GetMyCar()之类的命令,以便先发布汽车,如果你在未完成的汽车上打Mpg,你会得到一个例外,这是完全有道理的。如果你使用那种流畅的模式,它仍然会很好看。

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

好的,这是我的看法。但是,如果您不喜欢建设者,还有两种建议可供您选择。

1)如果用户在设置MpgWeight之前调用Fuel,则抛出异常,并显示解释该情况的消息。还要添加Mpg方法的正确文档。

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

答案 1 :(得分:8)

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

这已经在SO中用代码示例回答了。请参阅Conditional Builder Method Chaining Fluent Interface

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