Hi i'm trying to understand how i could build a readable and also error preventing Fluent-API without to much restriction for the User
to hold it simple let's say we want to change the following class to be fluent
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 the user should only be able to access Mpg()
if Weight()
and Fuel()
got called first also the position of HorsePower()
is irrelevant.
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 a easy way to do this without a big bunch of interfaces?
I also doesn't how to implement this nested interfaces in the right way
Here are the interfaces i currently 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 Houldsworth :-)
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();
Fluent Ribbon User Interface The Fluent interface makes it easier to find powerful features by replacing menus and toolbars with a Ribbon that organizes and presents capabilities in a way that corresponds more directly to how people work.
A fluent interface provides an easy-readable, flowing interface, that often mimics a domain specific language. Using this pattern results in code that can be read nearly as human language.
A fluent interface is an object-oriented API that depends largely on method chaining. The goal of a fluent interface is to reduce code complexity, make the code readable, and create a domain specific language (DSL). It is a type of method chaining in which the context is maintained using a chain.
Fluent interfaces help the user of your API to work with an object and it's methods in a more concise manner and can therefore make your API simpler and more desirable to use. Assume a basic Task object, which might look something like this: class Task(object): def name(self, name): self.
One alternative could be to invoke all operations on Mpg() which will allow the other operations to be conditional.
This is already answered in SO with a code sample. Please refer to Conditional Builder Method Chaining Fluent Interface
The post indicates, instead of Interfaces, the same might be achieved using constructors, with a calling method that makes all others operations conditional.
Fluent API is a nice thing, but I would go a different way in your case. Building a car draws me more towards the Builder pattern. That way you would hide a car being composed in a factory (not the factory method pattern) which accepts commands like you have now, but do not accept questions.
No manufacturer lets you know details about their new car unless it is completed and prepared to be announced. So you would have to send a command like GetMyCar()
for releasing a car first and it would perfectly make sense that if you call Mpg
on unfinished car you would get an exception. And it would still look good if you use that fluent pattern.
var builder = new CarBuilder();
// each building method returns `CarBuilder`
builder.BuildFrames(size).BuildChassis().AppendWheels(4)...
Ok, that's my opinion. However there are two more suggestions for your current situation you can choose from if you don't like the builder.
1) If user calls Mpg
before Weight
and Fuel
are set, thrown an exception with a message explaining the situation. Also add a proper documentation of the Mpg
method.
2) Make a constructor take all required parameters for other properties. This is, in my opinon, a better solution than the first one, because is states from the very start what you can expect.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With