I have been trying to design a fluent interface for one of my frameworks and it seems that I can't understand one piece of the puzzle. I understand that I can use interfaces and classes to kind of drive which methods I want to call. However consider the following scenario.
Lets Say I have a Person class and I want to be able to do something like
Person.WithName("Steve").WithAge(18).Save();
Simmilarly I also want the caller of my API to do something like
Person.WithName("Steve").Save();
or
Person.WithAge(18).Save();
But I don't want the user to call the save method alone like
Person.Save();
Now if I want to design such an API how can I implement it? If i return instance of Person class from WithName and WithAge methods then I have to put the Save method in the Person class as well which means user can call it directly.
As you indicated, you can use interfaces to control what is visible. Using explicit interface implementation lets you hide methods in some cases and expose them in others. It also lets you have more than one method with the same signature.
In this case, we have a private constructor, so the Person can only be created using one of the static entry points. Once we have either a name or an age, we return an instance of person, and it is valid to call WithName
, WithAge
or Save
.
public class Person : IPersonBuilder
{
private string _name;
private int? _age;
private Person() { }
public static IPersonBuilder WithName(string name)
{
return ((IPersonBuilder)new Person()).WithName(name);
}
public static IPersonBuilder WithAge(int age)
{
return ((IPersonBuilder)new Person()).WithAge(age);
}
IPersonBuilder IPersonBuilder.WithName(string name)
{
_name = name;
return this;
}
IPersonBuilder IPersonBuilder.WithAge(int age)
{
_age = age;
return this;
}
public void Save()
{
// do save
}
}
public interface IPersonBuilder
{
IPersonBuilder WithName(string name);
IPersonBuilder WithAge(int age);
void Save();
}
If Person
is a class with meaning beyond the fluent interface -- it is some kind of entity -- then I would create a single static entry point that returns a PersonBuilder
object and move all of the rest of the fluent concerns out of Person
.