Afraz Ali
Afraz Ali

Reputation: 2752

How to design a Fluent Interface?

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.

Upvotes: 3

Views: 815

Answers (2)

Jay
Jay

Reputation: 57919

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.

Upvotes: 5

Steven Wexler
Steven Wexler

Reputation: 17279

You probably want a distinction between creation and attribute setting. Maybe you want something like:

public interface IPerson
{
    IPerson WithName(string name);
    IPerson WithAge(int age);
}

public class Person : IPerson
{
    //You can also add required parameters here.  That'll 
    //ensure that a person is not saved before his specifications
    //are atleast minimally specified.
    public Person() { }
}

new Person().WithAge(18).WithName("Steven").Save();

Or if you just wanted developers to be able to build a person without encouraging modification the person after built.

public interface IPersonBuilder
{
    IPersonBuilder WithName(string name);
    IPersonBuilder WithAge(int age);
    IPerson Save()
}

public interface IPerson
{
    public string Name { get; }
    public int Age { get; }
}

public class PersonBuilder
{
    public PersonBuilder() { }
}

new PersonBuilder().WithAge(18).WithName("Steven").Save();

Upvotes: 4

Related Questions