3

I've tried several times and I keep getting myself into a loop. How would you change this code to a generics based approach? This code follows the Gang of Four Composite Pattern.

public abstract class AdjacencyTreeBase
{
    public AdjacencyTreeBase(int entityId, int entityTypeId)
    {
        EntityId = entityId;
        EntityTypeId = entityTypeId;
    }

    public long? Id { get; set; }
    public int? SystemId { get; set; }
    public int EntityId { get; set; }
    public int EntityTypeId { get; set; }
    public bool? isActive { get; set; }
    public long? lft { get; set; }
    public long? rgt { get; set; }

    public abstract void AddChild(AdjacencyTreeBase c);
    public abstract void RemoveChild(AdjacencyTreeBase c);
    public abstract List<AdjacencyTreeBase> ListChildren();
    public abstract void AddChildren(List<AdjacencyTreeBase> c);
    public abstract void ReplaceChildren(List<AdjacencyTreeBase> c);
}

public class AdjacencyTree : AdjacencyTreeBase
{
    private List<AdjacencyTreeBase> _children = new List<AdjacencyTreeBase>();
    public List<AdjacencyTreeBase> Children { get { return _children; } set { _children = value; } }

    public AdjacencyTree(int entityId, int entityTypeId) : base(entityId, entityTypeId) { }

    public override void AddChild(AdjacencyTreeBase component)
    {
        _children.Add(component);
    }
    public override void AddChildren(List<AdjacencyTreeBase> c)
    {
        _children = c;
    }
    public override void ReplaceChildren(List<AdjacencyTreeBase> c)
    {
        _children = c;
    }
    public override void RemoveChild(AdjacencyTreeBase component)
    {
        _children.Remove(component);
    }
    public override List<AdjacencyTreeBase> ListChildren()
    {
        return _children;
    }
}

public class AdjacencyAgency : AdjacencyTree
{
    public string agency_name { get; set; }
    public string customer_number { get; set; }
    public string agency_type { get; set; }

    public AdjacencyAgency(int entityId, int entityTypeId) : base(entityId, entityTypeId)
    {
    }
}

public class AdjacencyUser : AdjacencyTree
{
    public string officer_number { get; set; }
    public string last_name { get; set; }
    public string first_name { get; set; }
    public string middle_initial { get; set; }

    public AdjacencyUser(int entityId, int entityTypeId) : base(entityId, entityTypeId)
    {
    }
}

public class AdjacencyClient : AdjacencyTree
{
    public string last_name { get; set; }
    public string first_name { get; set; }
    public string middle_initial { get; set; }
    public string ssn { get; set; }

    public AdjacencyClient(int entityId, int entityTypeId) : base(entityId, entityTypeId)
    {
    }
}

A sample of instantiating this object map:

public List<AdjacencyTreeBase> CreateSample()
{
    // build bottom of tree objects...
    var client1 = new AdjacencyClient(1, 4)
    {
        first_name = "Pic'nic",
        last_name = "Basket #1",
        ssn = "123-45-6789"
    };
    var client2 = new AdjacencyClient(2, 4)
    {
        first_name = "Pic'nic",
        last_name = "Basket #2",
        ssn = "234-56-7890"
    };
    var client3 = new AdjacencyClient(3, 4)
    {
        first_name = "Bear",
        last_name = "Cave",
        ssn = "345-67-8901"
    };
    var client4 = new AdjacencyClient(4, 4)
    {
        first_name = "Picnic",
        last_name = "Table",
        ssn = "456-78-9012"
    };

    // build the next level up and add the children...
    var officer1 = new AdjacencyUser(1, 3)
    {
        first_name = "Yogi",
        last_name = "Bear",
        officer_number = "YB123"
    };
    officer1.AddChild(client1);
    officer1.AddChild(client2);

    var officer2 = new AdjacencyUser(2, 3)
    {
        first_name = "Park",
        last_name = "Ranger",
        officer_number = "PR123"
    };
    officer2.AddChild(client3);
    officer2.AddChild(client4);

    // build the top of the tree and add the middle children...
    var agencyThatAlreadyExists = new AdjacencyAgency(1, 2)
    {
        agency_name = "Jellystone",
        agency_type = "Park",
    };
    agencyThatAlreadyExists.AddChild(officer1);
    agencyThatAlreadyExists.AddChild(officer2);

    return agencyThatAlreadyExists;
}

While my sample is pretty simple, our entity structure is not quite so simple. We currently have 7 different entities and pretty much any type of entity can be a child of any type of entity and its siblings could be various types as well.

TIA

EDIT: To try to clarify: The children (and children of children) can be of any entity type (agency, user, officer, client, etc). While all entities have a base of properties in common, the rest of each object is different from one another. When pulling from the database, I may make a request for an agency and want the entire hierarchy underneath that one agency. Direct descendants could include all types, the each child could have descendants which include all types. Very messy, very flexible.

1
  • Your actual question here is unclear. What do you want? Based on your final paragraph it sounds like to you just want to restrict the final classes to only allow the one type (themselves) to be added. But the CreateSample method seems to suggest that you want to make a composite type. Can you be clearer on the question you're asking? Commented Mar 11, 2016 at 1:17

2 Answers 2

1

This works to make your class hierarchy strongly-typed & comply with the CreateSample code:

public abstract class AdjacencyTreeBase<T> where T : AdjacencyTreeBase<T>
{
    public AdjacencyTreeBase(int entityId, int entityTypeId)
    {
        EntityId = entityId;
        EntityTypeId = entityTypeId;
    }

    public long? Id { get; set; }
    public int? SystemId { get; set; }
    public int EntityId { get; set; }
    public int EntityTypeId { get; set; }
    public bool? isActive { get; set; }
    public long? lft { get; set; }
    public long? rgt { get; set; }

    public abstract void AddChild(T c);
    public abstract void RemoveChild(T c);
    public abstract List<T> ListChildren();
    public abstract void AddChildren(List<T> c);
    public abstract void ReplaceChildren(List<T> c);
}

public abstract class AdjacencyTree : AdjacencyTreeBase<AdjacencyTree>
{
    private List<AdjacencyTree> _children = new List<AdjacencyTree>();
    public List<AdjacencyTree> Children { get { return _children; } set { _children = value; } }

    public AdjacencyTree(int entityId, int entityTypeId) : base(entityId, entityTypeId) { }

    public override void AddChild(AdjacencyTree component)
    {
        _children.Add(component);
    }
    public override void AddChildren(List<AdjacencyTree> c)
    {
        _children = c;
    }
    public override void ReplaceChildren(List<AdjacencyTree> c)
    {
        _children = c;
    }
    public override void RemoveChild(AdjacencyTree component)
    {
        _children.Remove(component);
    }
    public override List<AdjacencyTree> ListChildren()
    {
        return _children;
    }
}

public class AdjacencyAgency : AdjacencyTree
{
    public string agency_name { get; set; }
    public string customer_number { get; set; }
    public string agency_type { get; set; }

    public AdjacencyAgency(int entityId, int entityTypeId) : base(entityId, entityTypeId)
    {
    }
}

public class AdjacencyUser : AdjacencyTree
{
    public string officer_number { get; set; }
    public string last_name { get; set; }
    public string first_name { get; set; }
    public string middle_initial { get; set; }

    public AdjacencyUser(int entityId, int entityTypeId) : base(entityId, entityTypeId)
    {
    }
}

public class AdjacencyClient : AdjacencyTree
{
    public string last_name { get; set; }
    public string first_name { get; set; }
    public string middle_initial { get; set; }
    public string ssn { get; set; }

    public AdjacencyClient(int entityId, int entityTypeId) : base(entityId, entityTypeId)
    {
    }
}

Then CreateSample needs to be modified like this:

public List<AdjacencyTree> CreateSample()
{
    // build bottom of tree objects...
    var client1 = new AdjacencyClient(1, 4)
    {
        first_name = "Pic'nic",
        last_name = "Basket #1",
        ssn = "123-45-6789"
    };
    var client2 = new AdjacencyClient(2, 4)
    {
        first_name = "Pic'nic",
        last_name = "Basket #2",
        ssn = "234-56-7890"
    };
    var client3 = new AdjacencyClient(3, 4)
    {
        first_name = "Bear",
        last_name = "Cave",
        ssn = "345-67-8901"
    };
    var client4 = new AdjacencyClient(4, 4)
    {
        first_name = "Picnic",
        last_name = "Table",
        ssn = "456-78-9012"
    };

    // build the next level up and add the children...
    var officer1 = new AdjacencyUser(1, 3)
    {
        first_name = "Yogi",
        last_name = "Bear",
        officer_number = "YB123"
    };
    officer1.AddChild(client1);
    officer1.AddChild(client2);

    var officer2 = new AdjacencyUser(2, 3)
    {
        first_name = "Park",
        last_name = "Ranger",
        officer_number = "PR123"
    };
    officer2.AddChild(client3);
    officer2.AddChild(client4);

    // build the top of the tree and add the middle children...
    var agencyThatAlreadyExists = new AdjacencyAgency(1, 2)
    {
        agency_name = "Jellystone",
        agency_type = "Park",
    };
    agencyThatAlreadyExists.AddChild(officer1);
    agencyThatAlreadyExists.AddChild(officer2);

    return new List<AdjacencyTree>() { agencyThatAlreadyExists };
}
Sign up to request clarification or add additional context in comments.

2 Comments

Added EDIT to bottom of question. My CreateSample is just a small preview of "what can be". You basically have it: at the top is a single entity (any type). That entity can have children (of any type). Those children can have children (of any type).
@KeithBarrows - I've updated with some code that I think works for your code.
0

I guess I'm not totally sure what you're hoping to accomplish, but I can tell you this much.

First off, C# doesn't offer a good way to do exactly what I understand you're looking for. You won't be able to replicate what you've got using generics, because you can't inherit from a generic type argument (which makes sense, from a conflict-avoidance perspective).

That said, what I would do, is something like this:

public class AdjacencyTree<T> : AdjacencyTree
{
    public AdjacencyTree(int entityId, int entityTypeId) : base(entityId, entityTypeId) { }

    public T Value { get; set; }
}

Of course, you're adding an extra layer of properties here. You could call that a good or a bad thing, but that's the best there is.

You'll, of course, then need to instantiate them like this:

var client1 = new AdjacencyTree<Client>(1, 4)
{
    Value = new Client()
    {
        first_name = "Pic'nic",
        last_name = "Basket #1",
        ssn = "123-45-6789"
    }
};

It's up to you whether this is better or worse, but it does have the distinct advantage that you can play with Client instances without touching the collection type.

If you want to be extra clever, you could add a method like this:

public class AdjacencyTree<T> : AdjacencyTree
{
    // ...

    public void AddChild<TChild>(int entityId, int entityTypeId, TChild child)
    {
        var child = new AdjacencyTree<TChild>(entityId, entityTypeId)
        {
            Value = child
        };
        this.AddChild(child);
    }
}

But that's really up to whether it would be useful. Some might object to adding this, as it unnecessarily duplicates the constructor arguments. But that's, of course, up to you. This is just another example.

Beyond that, I'd be tempted to drop entityTypeId from everything, unless you absolutely need it. That's up to how you want to use it, but I left it here based on your implementation. It seems like you could infer it based on T, but I wasn't sure why you needed it in your implementation either.

Even without generics being included, by my understanding, your existing pass-through constructor could look like this:

public AdjacencyUser(int entityId) : base(entityId, 3)

Thereby removing some redundancy. Of course, this is dependent on the assumption that two "User" types will always have the same EntityTypeId, which may or may not be fair.

I'm not sure if I've actually answered your question here, but hopefully it leads you in a bit of the right direction!

4 Comments

For each EntityTypeId, EntityIds are unique. That cannot be said without EntityTypeIds. Part of a 10+ year growth challenge. Started with just Users, then added Agencies & clients. That is about the point someone else "cleverly" deemed each Entity's ID (UserId, ClientId, etc) could be mapped to a compound key to make mapping relationships easier. Since then, devices, customers and more have been added. Let me play tomorrow to see if your example makes it easier to read/use. If so, this will be the answer.
@KeithBarrows Fair enough. Legacy stuff's never fun. My point was more that you don't need to accept the typeId in the constructor. I added an example in my last code block there. Of course, that's pretty tangential, and it's based on assumptions that may not be true. Just a suggestion.
But yeah, as for generics, they do change the flow of your code. Adding an extra property level has advantages (like, your entity could have a Children property if you wanted, whereas that would cause a conflict now) and disadvantages (extra typing? I don't really mind). I'd go for this, honestly, just for readability, but the biggest use-case I can imagine would come in the use of an ORM. If EF (or whatever) is serializing out User objects for you, you wouldn't have to map them over to this node type, which is convenient. Beyond that, it's just semantics.
I opted for the two parameters in the constructor as that is always required. There are zero situations where any object would be instantiated without at least those 2 pieces of data.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.