I'm trying to model family relationships in a OO way, i.e. As we update the relationships between two individuals our model must be able to find the people given a person and the type of relationship
• Given a ‘name’ and a ‘relationship’, you should output the people corresponding to the relationship in the order in which they were added to the family tree.
• You should be able to add a child to any family in the tree through the mother.
Sample input ADD_CHILD Chitra Aria Female GET_RELATIONSHIP Lavnya Maternal-Aunt GET_RELATIONSHIP Aria Siblings Sample Output CHILD_ADDITION_SUCCEEDED Aria Jnki Ahit
Full text of the problem here
My approach is mostly from this SO question.
- The relationship between two individuals is maintained as a graph using the adjacency list representation in the
FamilyGraph
class. PersonStore
acts as a repository of the persons involved.BaseRelationships
is a static class which provides extension methods on thePerson
class for the intrinsic relationships that can be used to build to find any sort of relationships.Relationships
is the public facing API which has all the relations that our model can currently figure out.
Person
class
namespace Family.Implementation
{
public class PersonStore : IPersonStore
{
private Dictionary<string, Person> people;
public PersonStore()
{
people = new Dictionary<string, Person>();
}
public void Add(Person person)
{
if(Contains(person.Name))
{
throw new ArgumentException($"{person.Name} is already present");
}
people.Add(person.Name, person);
}
public void Add(IEnumerable<Person> people)
{
foreach (var person in people)
{
try
{
Add(person);
}
catch (Exception)
{
throw;
}
}
}
public bool Contains(string personName)
{
return people.ContainsKey(personName);
}
public IEnumerable<Person> GetPeople(List<string> people)
{
List<Person> result = new List<Person>();
people.ForEach(person => {
try
{
Person personObject = GetPerson(person);
result.Add(personObject);
}
catch (Exception)
{
throw;
}
});
return result;
}
public Person GetPerson(string personName)
{
Person person;
bool result = people.TryGetValue(personName, out person);
if(!result)
{
throw new ArgumentException($"{personName} isn't found");
}
return person;
}
}
}
FamilyGraph
class
namespace Family.Implementation
{
public class FamilyGraph : IFamilyGraph
{
private Dictionary<Person, PersonRelationships> Families;
public FamilyGraph()
{
Families = new Dictionary<Person, PersonRelationships>();
}
public IPersonStore PersonStore { get { return ServiceLocator.GetService<IPersonStore>(); } }
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;
}
}
private void AddSpouseRelationship(Edge edge)
{
PersonRelationships sourcePersonRelationships, targetPersonRelationships;
Families.TryAdd(edge.Source, new PersonRelationships());
Families.TryAdd(edge.Target, new PersonRelationships());
Families.TryGetValue(edge.Source, out sourcePersonRelationships);
Families.TryGetValue(edge.Target, out targetPersonRelationships);
if (targetPersonRelationships.Spouse == null && sourcePersonRelationships.Spouse == null)
{
targetPersonRelationships.AddSpouse(edge.Source);
sourcePersonRelationships.AddSpouse(edge.Target);
}
else
{
throw new InvalidOperationException($"Cannot add spouse");
}
}
private void AddParentRelationship(Edge edge)
{
PersonRelationships sourcePersonRelationships, targetPersonRelationships;
Families.TryAdd(edge.Source, new PersonRelationships());
Families.TryAdd(edge.Target, new PersonRelationships());
Families.TryGetValue(edge.Source, out sourcePersonRelationships);
Families.TryGetValue(edge.Target, out targetPersonRelationships);
if (targetPersonRelationships.CanAddParent(edge.Source))
{
targetPersonRelationships.AddParent(edge.Source);
sourcePersonRelationships.AddEdge(edge);
}
else
{
throw new InvalidOperationException($"Cannot add parents to {edge.Target.Name}");
}
}
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;
}
}
}
and the related DTO's PersonRelationship
, Person
and EdgeInput
namespace Family.Implementation
{
public sealed class PersonRelationships : IPersonRelationships
{
public List<Edge> Edges { get; private set; }
public List<Person> Parents { get; private set; }
public Person Spouse { get; private set; }
public PersonRelationships()
{
Edges = new List<Edge>();
Parents = new List<Person>();
}
public PersonRelationships(List<Edge> edges, List<Person> persons)
{
Edges = edges;
Parents = persons;
}
public bool CanAddParent(Person parent)
{
if (Parents.Count() == 2)
return false;
return !Parents.Any(m => m.Gender == parent.Gender);
}
public void AddEdge(Edge edge)
{
Edges.Add(edge);
}
public void AddParent(Person parent)
{
Parents.Add(parent);
}
public void AddSpouse(Person spouse)
{
Spouse = spouse;
}
}
}
namespace Family.DTO
{
public class Edge
{
public Edge(Person source, Person target, RelationshipType relationshipType)
{
Source = source ?? throw new ArgumentNullException(nameof(source));
Target = target ?? throw new ArgumentNullException(nameof(target));
RelationshipType = relationshipType;
}
public Person Source { get; }
public Person Target { get; }
public RelationshipType RelationshipType { get; }
}
}
namespace Family.DTO
{
public class Person
{
public Person(string name, Gender gender)
{
if (string.IsNullOrWhiteSpace(name))
{
throw new ArgumentException("message", nameof(name));
}
Name = name;
Gender = gender;
}
public string Name { get; }
public Gender Gender { get; }
}
}
enum RelationshipType
namespace Family.Enums
{
public enum RelationshipType
{
Parent,
Spouse
}
}
and the BaseRelationships
that exposes the intrinsic functions as extention methods.
namespace Family.Implementation
{
public static class BaseRelationships
{
public static IFamilyGraph FamilyGraph = ServiceLocator.GetService<IFamilyGraph>();
public static IEnumerable<Person> Parents(this IEnumerable<Person> people, Gender? gender = null)
{
return people.SelectMany(m => m.Parents());
}
public static IEnumerable<Person> Children(this IEnumerable<Person> people, Gender? gender = null)
{
return people.SelectMany(m => m.Children(gender));
}
public static IEnumerable<Person> Siblings(this IEnumerable<Person> people, Gender? gender = null)
{
return people.SelectMany(m => m.Siblings(gender));
}
public static IEnumerable<Person> Spouse(this IEnumerable<Person> people)
{
return people.SelectMany(m => m.Spouse());
}
public static IEnumerable<Person> Parents(this Person person, Gender? gender = null)
{
List<Person> result = new List<Person>();
IPersonRelationships personRelationships = FamilyGraph.Get(person);
if(personRelationships == null)
{
return result;
}
IEnumerable<Person> parents = personRelationships.Parents
.Where(m => gender == null || m.Gender == gender);
result.AddRange(parents);
return result;
}
public static IEnumerable<Person> Children(this Person person, Gender? gender = null)
{
List<Person> result = new List<Person>();
IPersonRelationships personRelationships = FamilyGraph.Get(person);
List<Person> children = personRelationships.Edges
.Where(m => m.RelationshipType == RelationshipType.Parent)
.Where(m=> gender == null || m.Target.Gender == gender)
.Select(m => m.Target)
.ToList();
result.AddRange(children);
return result;
}
public static IEnumerable<Person> Siblings(this Person person, Gender? gender = null)
{
return person.Parents()
.Children(gender)
.Distinct()
.Where(m => !m.Equals(person));
}
public static IEnumerable<Person> Spouse(this Person person)
{
List<Person> result = new List<Person>();
IPersonRelationships personRelationships = FamilyGraph.Get(person);
if (personRelationships.Spouse != null)
{
result.Add(personRelationships.Spouse);
}
return result;
}
}
}
and the public facing API
namespace Family.Implementation
{
public sealed class Relationships : IRelationships
{
private IPersonStore PersonStore;
public Relationships()
{
var personStore = ServiceLocator.GetService<IPersonStore>();
PersonStore = personStore ?? throw new Exception("Could not load dependencies");
}
public Person GetPerson(string person)
{
Person personObject;
try
{
personObject = PersonStore.GetPerson(person);
}
catch (Exception)
{
throw;
}
return personObject;
}
public IEnumerable<Person> BrotherInLaw(string person)
{
return InLaws(person, Gender.Male);
}
public IEnumerable<Person> Daughter(string person)
{
return Children(person, Gender.Female);
}
public IEnumerable<Person> MaternalAunt(string person)
{
return UncleAndAunt(person, "Maternal", "Aunt");
}
public IEnumerable<Person> MaternalUncle(string person)
{
return UncleAndAunt(person, "Maternal", "Uncle");
}
public IEnumerable<Person> PaternalAunt(string person)
{
return UncleAndAunt(person, "Paternal", "Aunt");
}
public IEnumerable<Person> PaternalUncle(string person)
{
return UncleAndAunt(person, "Paternal", "Uncle");
}
public IEnumerable<Person> Siblings(string person)
{
Person personObject;
try
{
personObject = GetPerson(person);
}
catch (Exception)
{
throw;
}
return personObject.Siblings();
}
public IEnumerable<Person> SisterInLaw(string person)
{
return InLaws(person, Gender.Female);
}
public IEnumerable<Person> Son(string person)
{
return Children(person, Gender.Male);
}
private IEnumerable<Person> UncleAndAunt(string person, string direction, string uncleOrAunt)
{
Person personObject;
try
{
personObject = GetPerson(person);
}
catch (Exception)
{
throw;
}
Gender parentsGender = direction == "Maternal" ? Gender.Female : Gender.Male;
Gender uncleOrAuntGender = uncleOrAunt == "Aunt" ? Gender.Female : Gender.Male;
return personObject.Parents(parentsGender)
.Siblings(uncleOrAuntGender);
}
private IEnumerable<Person> Children(string person, Gender gender)
{
Person personObject;
try
{
personObject = GetPerson(person);
}
catch (Exception)
{
throw;
}
return personObject.Children(gender);
}
private IEnumerable<Person> InLaws(string person, Gender gender)
{
Person personObject;
try
{
personObject = GetPerson(person);
}
catch (Exception)
{
throw;
}
return personObject.Spouse().Siblings(gender);
}
}
}
- I user service locator instead of DI as I have to inject the
FamilyGraph
dependency on theBaseExtentions
static class to make them as extensions methods. Which unfortunately made testing really hard. - I believe extending the four intrinsic functions as extension methods make writing further relationships more intuitive and understandable
It would be great if you can comment on
- If the object composition is accurate enough.
- way to better inject
FamilyGraph
and yet have them as extension methods to thePerson
Object
Note I have ignored the IO part to call these relationships and also add the child for the review.
The entire code is also available on GitHub to make it easier to read.
Families
dictionary is an ordinary one so it shouldn't have theTryAdd
method. It belongs to theConcurrentDictionary
. Could you clarify this? \$\endgroup\$