0

I'm trying to build a fluent API for method chaining. These are the methods

public interface IBaseRelationships
{
    IEnumerable<Person> Parents(IEnumerable<Person> people);
    IEnumerable<Person> Children(IEnumerable<Person> people);
    IEnumerable<Person> Siblings(IEnumerable<Person> people);
}

And the implementation

   public class BaseRelationships : IBaseRelationships
    {
        public BaseRelationships(IFamilyGraph familyGraph)
        {
            FamilyGraph = familyGraph;
        }

        public IFamilyGraph FamilyGraph { get; }

        public IEnumerable<Person> Parents(IEnumerable<Person> people)
        {
            List<Person> result = new List<Person>();
            foreach (var person in people)
            {
                IPersonRelationships personRelationships = FamilyGraph.Get(person);
                result.AddRange(personRelationships.Parents);
            }
            return result;
        }
        public IEnumerable<Person> Children(IEnumerable<Person> people)
        {
            List<Person> result = new List<Person>();
            foreach (var person in people)
            {
                IPersonRelationships personRelationships = FamilyGraph.Get(person);
                List<Person> children = personRelationships.Edges.Where(m => m.RelationshipType == RelationshipType.Parent)
                    .Select(m => m.Target)
                    .ToList();
                result.AddRange(children);
            }
            return result;

        }
        public IEnumerable<Person> Siblings(IEnumerable<Person> people)
        {
            List<Person> result = new List<Person>();
            return result;
        }
    }
}

I'd like to do something like this.

var person = new List<Person> {new Person()};
var cousins = person.Parents().Siblings().Children();

I know with this current implementation that is not possible and I might have to write extension methods. For that, the classes which contain the extension methods have to be static hence I can't inject the FamilyGraph dependency.

With the current implementation if I return IBaseRelationships instead of IEnumerable<Person> I'll be able to get this to work. But I'm not sure how I'll be able to get the actual result (IEnumerable<Person>) out of it.

Any idea on how to build this for the methods in the interface would be great.

Edit. Addding FamilyGraph fore reference

public class FamilyGraph : IFamilyGraph
{
    private Dictionary<Person, PersonRelationships> Families;
    public FamilyGraph(IPersonStore personStore)
    {
        Families = new Dictionary<Person, PersonRelationships>();
        PersonStore = personStore;
    }
    public IPersonStore PersonStore { get; }

    public void Add(EdgeInput inputEdge)
    {
        Edge edge;
        try
        {
            edge = GetEdge(inputEdge);
        }
        catch (ArgumentException)
        {
            throw;
        }
        switch (edge.RelationshipType)
        {
            case Enums.RelationshipType.Parent:
                AddParentRelationship(edge);
                return;
            case Enums.RelationshipType.Spouse:
                AddSpouseRelationship(edge);
                return;
        }
    }
    public Edge GetEdge(EdgeInput inputEdge)
    {
        Person source, target;
        try
        {
            source = PersonStore.GetPerson(inputEdge.Source);
            target = PersonStore.GetPerson(inputEdge.Target);
        }
        catch (Exception)
        {

            throw;
        }
        return new Edge(source, target, inputEdge.RelationshipType);
    }
    public IPersonRelationships Get(Person person)
    {
        PersonRelationships personRelationships;
        Families.TryGetValue(person, out personRelationships);
        return personRelationships;
    }
}

public interface IPersonRelationships
    {
        List<Edge> Edges { get; }
        List<Person> Parents { get; }
        Person Spouse { get; }

        void AddEdge(Edge edge);
        void AddParent(Person parent);
        void AddSpouse(Person spouse);
        bool CanAddParent(Person parent);
    }

public interface IPersonStore
{
    void Add(Person person);
    bool Contains(string personName);
    Person GetPerson(string personName);
}
thebenman
  • 1,621
  • 14
  • 35
  • What is FamlyGraph? Is it a store with all the relationships? – Avin Kavish Jun 15 '19 at 13:35
  • 1
    Extension methods would probably be ideal, perhaps there's a different way to inject the dependency? I hate to suggest a Service Locator, but it might do the trick. Or, failing that, perhaps go with your idea to return `IBaseRelationships` and then just add one more method to the interface which returns the final `IEnumerable`. It limits the fluent syntax because once the final one is called you'd need to start a new chain any time after it, but that may not end up being so bad in the usage. – David Jun 15 '19 at 13:44
  • 1
    assuming you can't control the person and familygraph classes it might be easiest to introduce extension methods to transform to/from the releastionship class so it would look like: person.GetRelationships(familygraph).Parents().Siblings().Children().ToPersons() @David - didn't see your comment to start, but I believe this is essentially what you mean as well. – MikeJ Jun 15 '19 at 13:55
  • @thebenman `IPersonRelationships` ? just the public interface is enough – Avin Kavish Jun 16 '19 at 06:54
  • @AvinKavish Added – thebenman Jun 16 '19 at 06:56
  • AFAIK this is not a place you should be using a fluent API. Fluent APIs generally return the same object `(new Foo()).color('red).width(100).style('bold')`. Each of those is returning the original object. In your case it seems you want to return something else, in particular something else that can fail. In that case fluent is the wrong style of API. For reference the wikipedia entry says a fluent interface is "self-referential, where the new context is equivalent to the last context." – gman Jun 16 '19 at 07:19

1 Answers1

2

Let's start at the type of public API you want to expose, I've made a small adjustment to allow individuals as well as collectives.

var person = new Person();
var people = new List<Person> { person };
var cousins = person.Parents().Siblings().Children();
// Or
var cousins = people.Parents().Siblings().Children();

Since you want to start chaining on a person/people, this person needs to be aware of the FamilyGraph they belong to.

public class Person {

    public FamilyGraph FamilyGraph { get; set; }

    IEnumerable<Person> Parents() => FamilyGraph.Get(person).Parents;

    IEnumerable<Person> Children() => 
        FamilyGraph.Get(person).Edges
          .Where(m => m.RelationshipType == RelationshipType.Parent)
          .Select(m => m.Target)
          .ToList();

    IEnumerable<Person> Siblings() => FamilyGraph.Get(person)./* your logic here */;
}


public static class PeopleExtensions 
{
    public static IEnumerable<Person> Parents(this IEnumerable<Person> people) =>
        people.SelectMany(person => person.Parents()).ToList();

    public static IEnumerable<Person> Siblings(this IEnumerable<Person> people) =>
        people.SelectMany(person => person.Siblings()).ToList();

    public static IEnumerable<Person> Children(this IEnumerable<Person> people) =>
        people.SelectMany(person => person.Children()).ToList();

}
Avin Kavish
  • 8,317
  • 1
  • 21
  • 36
  • This is great. But making the `Person` object `FamilyGraph` aware is hard since it is the other way around. `FamilyGraph` depends on the `Person` objects. And I do not feel great about passing the `FamilyGraph` with every method call. – thebenman Jun 16 '19 at 06:21
  • Then you can't chain it starting at a person. You will have to start at the `FamliyGraph` and have an intermediary type that carries the `FamilyGraph` forward. I.e `FamilyGraph.query(person).Parents().Siblings().Cousins()` query would return something like `IFamilyGraphQueryable` – Avin Kavish Jun 16 '19 at 06:27
  • Can you show me the `FamilyGraph` class and the return type of `FamilyGraph.get` – Avin Kavish Jun 16 '19 at 06:36