Understanding how to create a free interface

Hi, I’m trying to understand how I could create readable as well as prevent Fluent-API errors without any special restrictions for the user.

to keep it simple, let them say that we want to change the next class to be free

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; } } 

the problem in this case is the user should have access only to Mpg() if Weight() and Fuel() received the call first, and the position of HorsePower() does not matter.

Samples:

 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(); 

Is there an easy way to do this without a lot of interfaces?
I also do not know how to properly implement these nested interfaces.

Below are the interfaces I created

 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 

EDIT for Adam Haldsworth :-)

  • Is the interface higher than good or is there an easier way to do this , but keep the constraint for Mpg ()?
  • How to implement the interface above to do this?

      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(); 
+8
c # fluent-interface fluent
source share
2 answers

One alternative may be to call all operations on Mpg (), which will allow other operations to be conditional.

This has already been answered in SO with a sample code. Please refer to the Continuous Conditional Method Builder Interface

The message indicates that instead of interfaces, this can be achieved using constructors with a calling method that makes all other operations conditional.

+6
source share

The Fluent API is a good thing, but I would go differently in your case. Building a car draws me more to the builder pattern . Thus, you will hide a car consisting of a factory (and not a factory method template) that accepts commands like you, but does not accept questions.

No manufacturer will let you know about your new car if it is not completed and is not ready for announcement. Therefore, you need to send a command, for example GetMyCar() , in order to let go of the car first, and it is clear that if you name Mpg on an unfinished car, you will get an exception. And it will still look good if you use this smooth pattern.

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

Ok, this is my opinion. However, there are two more suggestions for your current situation that you can choose if you don't like the builder.

1) If the user calls Mpg before Weight and Fuel set, an exception is thrown with a message explaining the situation. Also add the correct documentation of the Mpg method.

2) Create a constructor to get all the necessary parameters for other properties. This, in my opinion, is a better solution than the first, because from the very beginning it is what you can expect.

+7
source share

All Articles